美文网首页
React 小书笔记

React 小书笔记

作者: 喵呜Yuri | 来源:发表于2019-03-31 10:38 被阅读0次

    http://huziketang.mangojuice.top/books/react/ react小书
    这是我读React 小书的一些笔记

    开始

    只有改变了state状态,也就是使用了this.setState(...)才能导致重新调用render。
    记住,只要你要写 React.js 组件,那么就必须要引入这两个东西:React和组件继承 Component 类

    import React, { Component } from 'react'
    

    render的用法:

    元素变量:

    render () {
      const isGoodWord = true
      const goodWord = <strong> is good</strong>
      const badWord = <span> is not good</span>
      return (
        <div>
          <h1>
            React 小书
            {isGoodWord ? goodWord : badWord}
          </h1>
        </div>
      )
    }
    

    条件返回:

    render () {
      const isGoodWord = true
      return (
        <div>
          <h1>
            React 小书
            {isGoodWord
              ? <strong> is good</strong>
              : <span> is not good</span>
            }
          </h1>
        </div>
      )
    }
    

    ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。
    自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。
    on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。
    <div></div>属于HTML 的标签,<Mytitle></Mytitle>属于组件标签

    获取点击实例:e.target

    class Title extends Component {
      handleClickOnTitle (e) {
        console.log(e.target.innerHTML)
      }
    
      render () {
        return (
          <h1 onClick={this.handleClickOnTitle}>React 小书</h1>
        )
      }
    }
    

    点击获取实例:this,传参

    class Title extends Component {
      handleClickOnTitle (word, e) {
        console.log(this, word)
      }
    
      render () {
        return (
          <h1 onClick={this.handleClickOnTitle.bind(this, 'Hello')}>React 小书</h1>
        )
      }
    }
    

    setState 接受函数参数:

    你会发现两次打印的都是 false,即使我们中间已经 setState 过一次了。这并不是什么 bug,只是 React.js 的 setState 把你的传进来的状态缓存起来,稍后才会帮你更新到 state 上,所以你获取到的还是原来的 isLiked。

      handleClickOnLikeButton () {
        console.log(this.state.isLiked)
        this.setState({
          isLiked: !this.state.isLiked
        })
        console.log(this.state.isLiked)
      }
    

    所以如果你想在 setState 之后使用新的 state 来做后续运算就做不到了
    可以这样做:

      handleClickOnLikeButton () {
        this.setState((prevState) => {
          return { count: 0 }
        })
        this.setState((prevState) => {
          return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
        })
        this.setState((prevState) => {
          return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
        })
        // 最后的结果是 this.state.count 为 3
      }
    

    上面我们进行了三次 setState,但是实际上组件只会重新渲染一次,而不是三次,这就是setState的合并,并不需要担心多次进行 setState 会带来性能问题。

    默认配置 defaultProps:

    如果this.props.likedText没有被定义的时候,就会去取defaultProps.likedText

     static defaultProps = {
        likedText: '取消',
        unlikedText: '点赞'
      }
    

    props 不可变:

     handleClickOnLikeButton () {
        this.props.likedText = '取消'
      }
    

    这样的代码会引发报错,props是不可变的,如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑。
    props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。

    state和props

    尽量少地用 state,尽量多地用 props。
    没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

    渲染列表:

    class Index extends Component {
      render () {
        return (
          <div>
            {users.map((user) => {
              return (
                <div>
                  <div>姓名:{user.username}</div>
                  <div>年龄:{user.age}</div>
                  <div>性别:{user.gender}</div>
                  <hr />
                </div>
              )
            })}
          </div>
        )
      }
    }
    

    更简化:

    class User extends Component {
      render () {
        const { user } = this.props
        return (
          <div>
            <div>姓名:{user.username}</div>
            <div>年龄:{user.age}</div>
            <div>性别:{user.gender}</div>
            <hr />
          </div>
        )
      }
    }
    
    class Index extends Component {
      render () {
        return (
          <div>
            {users.map((user) => <User key={user.id} user={user} />)}
          </div>
        )
      }
    }
    

    记得用key,没有id的话可以用计数器来表示

     {users.map((user, i) => <User key={i} user={user} />)}
    

    想想使用key的目的,这种方法只是掩耳盗铃

    本着React.js 团队的做法,把事情搞复杂一些,提高数据修改的门槛。解决“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。是action,dispatch出现的重要原因

    纯函数:

    1.函数的返回结果只依赖于它的参数。
    2.函数执行过程里面没有副作用。
    实例1:

    const a = 1
    const foo = (b) => a + b
    foo(2) // => 3
    

    这个不是纯函数,因为返回的结果不止和参数有关还和外部变量有关
    实例2:

    const a = 1
    const foo = (obj, b) => {
      obj.x = 2
      return obj.x + b
    }
    const counter = { x: 1 }
    foo(counter, 2) // => 4
    counter.x // => 2
    

    foo函数的执行对外部的 counter 产生了影响(obj.x产生了影响),它产生了副作用
    除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用。

    为什么要煞费苦心地构建纯函数?因为纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。不管何时何地,你给它什么它就会乖乖地吐出什么。如果你的应用程序大多数函数都是由纯函数组成,那么你的程序测试、调试起来会非常方便。

    es6 扩展运算符赋值

    一般我们不会直接改变state而是使用浅复制,创建一个NewState
    像这样:

    let newAppState1 = { // 新建一个 newAppState1
      ...appState , // 复制 newAppState1 里面的内容
      title: { // 用一个新的对象覆盖原来的 title 属性
        ...appState .title, // 复制原来 title 对象里面的内容
        color: "blue" // 覆盖 color 属性
      }
    }
    

    它的好处是什么?我们为什么要这么做?

    appState !== newAppState // true,两个对象引用不同,数据变化了,重新渲染
    appState.title !== newAppState.title // true,两个对象引用不同,数据变化了,重新渲染
    appState.content !== appState.content // false,两个对象引用相同,数据没有变化,不需要重新渲染
    

    高阶函数:

    高阶组件就是一个函数,传给它一个组件,在里面通过方法和传入的参数装饰一下,返回一个新的组件。

    redux:

    要注意的是,Redux 和 React-redux 并不是同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。

    reducer是纯函数
    redux的使用套路总结下来就是:

    // 定一个 reducer
    function reducer (state, action) {
      /* 初始化 state 和 switch case */
    }
    
    // 生成 store
    const store = createStore(reducer)
    
    // 监听数据变化重新渲染页面
    store.subscribe(() => renderApp(store.getState()))
    
    // 首次渲染页面
    renderApp(store.getState()) 
    
    // 后面可以随意 dispatch 了,页面自动更新
    store.dispatch(...)
    

    connect

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import { connect } from './react-redux'
    
    class Header extends Component {
      static propTypes = {
        themeColor: PropTypes.string
      }
    
      render () {
        return (
          <h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
        )
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        themeColor: state.themeColor
      }
    }
    Header = connect(mapStateToProps)(Header)
    
    export default Header
    

    我们可以这么理解这句话:

    Header = connect(mapStateToProps)(Header)
    

    Header中包含对共享数据的操作,以及根据state进行后续组件渲染,connect是一个包装Header组件的装饰函数,它需要这个原组件本身和与组件相关的state数据作为参数。
    还要传一个mapDispatchToProps作为参数

    const mapDispatchToProps = (dispatch) => {
      return {
        onSwitchColor: (color) => {
          dispatch({ type: 'CHANGE_COLOR', themeColor: color })
        }
      }
    }
    ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
    

    挂载阶段的生命周期:

    我们把 React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载(这个定义请好好记住)

    -> constructor()
    -> componentWillMount()
    -> render()
    // 然后构造 DOM 元素插入页面
    -> componentDidMount()
    

    一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行
    这里有段计时器的代码:

    class Clock extends Component {
      constructor () {
        super()
        this.state = {
          date: new Date()
        }
      }
    
      componentWillMount () {
      clearInterval(this.timer);//记得先清除定时器,避免重新渲染时定时器还在
        this.timer = setInterval(() => {
          this.setState({ date: new Date() })
        }, 1000)
      }
      ...
    }
    

    更新阶段的组件生命周期:

    1.shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
    2.componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
    3.componentWillUpdate():组件开始重新渲染之前调用。
    4.componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

    ref:

    React.js 并不能完全满足所有 DOM 操作需求,有些时候我们还是需要和 DOM 打交道。比如说你想进入页面以后自动 focus 到某个输入框,你需要调用 input.focus() 的 DOM API,比如说你想动态获取某个 DOM 元素的尺寸来做后续的动画,等等

    class AutoFocusInput extends Component {
      componentDidMount () {
        this.input.focus()
      }
    
      render () {
        return (
          <input ref={(input) => this.input = input} />
        )
      }
    }
    
    ReactDOM.render(
      <AutoFocusInput />,
      document.getElementById('root')
    )
    

    然后我们就可以在 componentDidMount 中使用这个 DOM 元素,并且调用 this.input.focus() 的 DOM API。整体就达到了页面加载完成就自动 focus 到输入框的功能
    也可以给组件加:

    <Clock ref={(clock) => this.clock = clock} />
    

    但ref这个属性能不用就不要用

    props.children:

    它定义了一种外层结构形式,然后你可以往里面塞任意的内容。有点类似于DOM操作中的append()

    class PropsChilden extends Component {
        render() {
           const childer = this.props.children;
            return (
                <div>
                    {childer.map((item,index)=>{
                    return <div key={index}>{item}</div>;
                    })}
                </div>
    
            );
        }
    
    }
    
    export default PropsChilden;
    

    用的时候:

     <PropsChilden>
                  <span>rick</span>
                  <span>zhangamie</span>
                  <span>react</span>
                  22132132
     </PropsChilden>
    

    但是这样是不可以的:

     <PropsChilden>
                [1,2,3,4]
     </PropsChilden>
    

    因为传过去的this.props.children是string类型,map函数自然会报错

    动态样式:

    style接受的是一个对象,要用驼峰命名方式,font-size写做fontSize

    <h1 style={{fontSize: '12px', color: this.state.color}}>React.js 小书</h1>
    

    dangerouslySetInnerHTML

    class Editor extends Component {
      constructor() {
        super()
        this.state = {
          content: '<h1>React.js 小书</h1>'
        }
      }
    
      render () {
        return (
          <div className='editor-wrapper'>
            {this.state.content}
          </div>
        )
      }
    }
    

    当你写这些以为会得到一个加粗黑体的一行字吗?不,你得到的是这个:


    image.png

    你应该这么写:

      render () {
        return (
          <div
            className='editor-wrapper'
            dangerouslySetInnerHTML={{__html: this.state.content}} />
        )
      }
    

    但是设置 innerHTML 可能会导致跨站脚本攻击(XSS),这个属性不必要的情况就不要使用。

    PropTypes 和组件参数验证

    在做大型应用的时候有PropTypes写作规范是很有益的,因为js是弱类型语言,是允许let a = {number:1};a = 1;这样的代码的。项目越大有些隐晦的bug会越不好找。我们需要一个规范及时矫正偏差
    安装:npm install --save prop-types

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    
    class Comment extends Component {
      static propTypes = {
        comment: PropTypes.object
      }
    
      render () {
        const { comment } = this.props
        return (
          <div className='comment'>
            <div className='comment-user'>
              <span>{comment.username} </span>:
            </div>
            <p>{comment.content}</p>
          </div>
        )
      }
    }
    

    这段代码是说:comment字段必须是一个object类型,如果你传入其他类型就会报错


    image.png

    也有该字段未赋值,undefined,我们可以这样写:

    static propTypes = {
      comment: PropTypes.object.isRequired
    }
    

    相关文章

      网友评论

          本文标题:React 小书笔记

          本文链接:https://www.haomeiwen.com/subject/rsjjbqtx.html