美文网首页
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 Native填坑之旅--Button篇

    React Native填坑之旅--Button篇React Native填坑之旅--动画React Native...

  • React Native填坑之旅--动画

    React Native填坑之旅--Button篇React Native填坑之旅--动画React Native...

  • React Native填坑之旅--HTTP请求篇

    React Native填坑之旅--Button篇React Native填坑之旅--动画React Native...

  • React填坑

    Virtual Dom 在浏览器环境下,DOM操作远比JS操作开销大,且项目越复杂,DOM操作的开销越大,为了提升...

  • React Native 填坑

    Animated官方示例 填坑 ListView前面有space 如果还是不行,则在ListView前加入一个空的...

  • RN安装注意事项

    React native填坑心路之检测安装 1.在cmd命令行新建一个项目:react-native init 项...

  • react-router模块化配置 以及React-Router

    因为公司的需要最近踏进了react坑,一直在挖坑填坑,在路由这一块折腾得不行。 直接进入主题,配置react-ro...

  • React Native 填坑之路

    以下是我在开发过程中遇到的一些问题以及解决方法总结: 1、升级Xcode11, React Native启动报错的...

  • React Native 填坑指南

    本文为 Marno 原创,转载必须保留出处!公众号【 aMarno 】,关注后回复 RN 加入交流群React N...

  • React Native 填坑记

    公司转 React Native 开发,从开始接触到现在也一个多月了,给我的感觉,RN 的确非常叼,JSX、ES6...

网友评论

      本文标题:React填坑

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