美文网首页
React填坑

React填坑

作者: Devildi已被占用 | 来源:发表于2017-11-26 22:53 被阅读0次

    Virtual Dom

    在浏览器环境下,DOM操作远比JS操作开销大,且项目越复杂,DOM操作的开销越大,为了提升DOM渲染的效率,需要遵循的原则是尽量减少DOM操作。于是Virtual Dom横空出世!
    Virtual Dom顾名思义,是使用JS模拟DOM结构;并把DOM变化的对比,放在JS层来做,以此来提高重绘性能(将开销大的Dom操作转化为高效的JS操作)

    <ul id='list'>
      <li class='item'>Item 1</li>
      <li class='item'>Item 2</li>
    </ul>
    
    //用JS模拟DOM如下:
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 1']
        },
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 1']
        }
      ]
    }
    

    snabbdom

    snabbdom为virtual dom的一个实现库,其核心API有2:
    h():创建virtual dom
    patch(vnode, mewnode):渲染前对比两个虚拟节点后并完成真实渲染

    diff

    virtual dom中(React、Vue)的diff算法起源于Linux的diff命令,用于比对文本的不同之处,是一个古老的命令。在virtual dom操作中,找出需要重新渲染的节点的过程,就是React中的diff算法。

    //简略模拟diff:
    function creatElement(vnode){
      var tag = vnode.tag
      var attrs = vnode.attrs || {}
      var children = vnode.children || {}
      if(!tag){
        return null
      }
      var elem = document.creatElement(tag)
      var attrName
      for (attrName in attrs) {
        if(attrs.hasOwnProperty(attrName)){
          elem.setAttribute(attrName, attrs[attrName])
        }
      }
      chlidren.forEach((childVnode) => {
        elem.appendChild(creatElement(childVnode))//递归
      })
      return elem
    }
    
    function updateChildren(Vnode, newVnode){
      var children = vnode.children || {}
      var newChildren = newVnode.children || {}
      children.forEach((childVnode, index) => {
        var newChildVnode = newChildren[index]
        if(childVnode.tag === newChildVnode.tag){
          updateChildren(childVnode, newChildVnode)
        } else {
          replace(childVnode, newChildVnode)
        }
      })
    }
    

    JSX

    在React中负责VIew的编写,其本质上是语法糖,由于浏览器无法识别JSX,实际运行时由React库中的核心函数React.creatElement将JSX翻译成JS,再由JS渲染为HTML,类似于virtual dom经由h()转为真实节点一样。
    React首次渲染时,调用patch(container, vnode)
    通过setState()函数再次渲染时,调用patch(newVnode, vnode)

    JSX自定义组件的解析过程

    1. React.creatElement(App, null)
      首先调用React的核心函数,传入自定义组件App
    2. var app = new App()
      Appclass,进行类的实例化
    3. return app.render()
      调用类实例的render()方法,并继续步骤1,直至没有自定义组件为止

    setState()方法

    • 异步
      setState()方法是异步的,这是因为一次操作中,可能执行多次setState,根据浏览器单线程的特性和性能上的考量,无需每次执行setState的时候都重新渲染,只需进行最后一次渲染即可。
    ......
    {
    setState({a: 1})
    setState({a: 2})
    setState({a: 3})//只执行最后一次,前两次不执行
    }
    ......
    
    • 过程
      1 每个React组件实例,均有继承自Component类的renderComponent方法。
      2 执行renderComponent方法会再次执行组件的render方法。
      3 render方法返回newVnode,同时系统缓存有preVnode的信息。
      4 执行patch(preVnode, newVnode)

    React生命周期相关

    组件生命周期

    shouldComponentUpdate

    React开发时,一个很奇妙的事情就是当stateprops未发生改变时,组件依然会重新渲染,所以当追求性能的时候,shouldComponentUpdate就派上了用场。shouldComponentUpdate生命周期函数是重渲染时render()函数调用前被调用的函数,它接受两个参数:nextPropsnextState,分别表示下一个props和下一个state。并且,当函数返回false时候,阻止接下来的render()的调用,阻止组件重渲染,而返回true时,组件照常重渲染。

    //通过对比属性来确定组件是否更新
    shouldComponentUpdate(nextProps,nextState){
      if(nextProps.numberObject.number == this.props.numberObject.number){
        return false
      }
      return true
    }
    

    一个警告

    Warning: setState(...): Can only update a mounted or mounting 
    component. This usually means you called setState() on an 
    unmounted component. This is a no-op. Please check the code for 
    the OrderTableList component.
    

    在组件生命周期中,设立标志位来检验组件是否挂载,可规避此警告

    componentDidMount(){
      this.mounted = true
      //some code
    }
    
    if(this.mounted){
      this.setState({})
    }
    
    componentWillUnmount(){
      this.mounted = false
    }
    

    react数据流

    在并不复杂的webapp中,其实是可以不用redux的!

    • 父组件向子组件传递数据,类似如下的思路
    <Father>
      <Son data={this.state.data}/>
    </Father>
    
    • 子组件向父组件传递数据
    <Father>
      <Son changeData={this.addData.bind(this)}/>
    </Father>
    
    //Father中有addData()操作数据,并将此方法向下传递下去(到Son)
    addData(){
      //do sth
    }
    
    //Son中有方法负责传递数据,调用this.props上传递下来的方法处理,并将自身数据传入其中
    do(){
      this.props.changeData(mydata)
    }
    
    • 子组件与子组件间传递数据
      这里说的是有公共父组件的应用场景,即将子组件一的数据上传到父组件,经由父组件中转后将数据下发到子组件二
    • ref
      当父组件需要直接操作子组件时,即调用子组件方法,可以使用ref
    <Father>
      <Son ref='loginAlert'/>
    </Father>
    ~~~~~~~~~
    ~~~~~~~~~
    this.refs.loginAlert.someFuc(this为Father组件上下文)
    

    关于父组件更新而子组件不更新的问题

    react中,当父组件通过props向子组件传递数据时,当父组件更新时,子组件会重新render,注意是重新render,子组件并不会完成卸载——加载过程,因此,constructor构造函数并不会再次执行,通过this.state语句自然不会传入props的值。这种情况下,子组件可以定义为“无状态”组件,直接使用this.props.data即可。

    Immutable.js

    var a = 'a'
    var b = a
    var b = 'b'
    console.log(a) //b
    

    修改引用型对象是一样不安全的行为

    var a = 'a'
    var b = Object.assign({}, a)
    console.log(a === b) //false
    

    可以使用ES6方法生成指向不同内存地址的新对象(concat(),...均可),可以使操作对象的行为变得相对安全,但是这种不断新开辟内存的行为会增大系统的开销,此时immutable.js隆重登场 ,其核心思想为,对象一经创建,其不会被改变;无论对其进行什么操作,都返回一个新对象;老对象始终不变,而新对象通过树形结构共享原则,既保留原来对象不变,又保留变化的这个部分,而因为每次变化仅为一部分,避免了深拷贝带来的性能损耗,同时可以进行任意时刻的数据回溯。

    Redux

    Redux数据流
    Redux数据流
    • storeRedux的核心,其集成了reducers和一些中间件,Action发出动作后,集成了reducersstore随即发生状态变化,然后数据便会从根容器(Provider)向下传递;由于数据在全局有且只有一份(Store),因此在React应用中频繁修改state容易引起数据混乱,因此在Redux数据流中,需要使用Immutable Data
    • Redux数据流中,一个Action有一个或者多个Reducer与之对应,多个ReducercombineReducer包裹并传入Store
    • 容器组件和视图组件


      容器和视图组件
    中间件
    const logger = (store) => (next) => (action) => {
      //do something
    } //科里化函数
    
    构建store
    import {creatStore, applyMiddleware} from 'redux'
    import {fromJS} from 'immutable'
    import thunk from 'redux-thunk'
    import creatLogger from 'redux-logger'
    import promiseMiddleware from 'redux-promise'
    
    import reducers from './reducers'
    const middlewares = [thunk , promiseMiddleware, creatLogger()]
    function configerStore( initialState = fromJS({}){
      const store = creatStore(reducers(), initialState,  applyMiddleware(...middlewares))
      if(module.hot){
        module.hot.accept( () => {
          store.replaceReducer(require('./reducers').default) //热重载更新reducers
        })
      }
      return store
    }
    export default configerStore
    
    connect

    用于建立store和actions的连接,使reducers可以接收到发出的actions,从而改变store中的数据,即将action和reducer都绑定在一个实例上。connect()接受4个参数:

    mapStateToProps #把store中的数据作为属性绑定到组件上,从而可以...this.props获取
    mapDispatchToProps #方法的绑定,从而可以this.props获取action调用
    mergeProps
    options
    

    Mobx

    Mobx数据流相较Redux而言,概念更少。Mobx基于一份数据进行修改(Redux数据基于浅拷贝,每次更新都生成新的数据对象,但是由于浅拷贝和虚拟DOM的存在,对于整体性能的影响有限),因其对store数据的修改可以使用JS修改普通对象的方式,过程比较灵活(不严谨)。

    const mobxStore = observable(
      count: 0,
      add: action(function(num){
        this.count += num
      })
    )
    mobxStore.add(1)
    
    • Mobx应用中用Class组织
    • Mobx重要概念
    @observable
    @action
    @computed
    autorun()//数据store更新时调用
    
    • 通过@inject('storeName')向组件注入数据
    • 通过@observer监听组件

    React16新特性

    • rollup打包,体积小
    • 使用Fiber重写
    • 一些实用的功能:
    1. error boundary,捕获React渲染过程中出现的错误,通过新的钩子函数:componentIDidCatch
    2. New render return types,可以在Render方法中返回Array和String,也可以使用Fragment代替没有实际意义的div
    3. Portals,用于创建浮层或者Popup组件
    4. Better sever-side rendering

    React中使用PropTypes进行类型检查

    从React15.5起,React内置的类型检测库开始作为独立的存在,开发者可以通过‘prop-types’类型检测库捕获更多的bug,PropTypes 输出了一系列的验证器,可以用来确保接收到的参数是有效的。当给 props 传递了一个不正确的值时,JavaScript控制台将会显示一条警告。

    import PropTypes from 'prop-types'
    
    class A extends React.Component {
       render() {
        return (
          <h1>Hello, {this.props.name}</h1>
        );
      }
    }
    
    A.PropTypes = {
      name: PropTypes.string
    }
    

    同一组件无法重新渲染的解决方案

    当使用路由切换时,不同的路由下对应的同一个组件 ,这时候React并不会重新渲染组件,此时只是传入的props有了一定的变化,这时需要利用React的钩子方法:componentWillReceiveProps,通过比对props,来完成新数据的获取,进而重新渲染组件。

    相关文章

      网友评论

          本文标题:React填坑

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