React16经常使用api解析以及原理剖析

React16经常使用api解析以及原理剖析

目录

  1. VueReact 两个框架的粗略区别对比
  2. react 16 版本常见 api
  3. react 生命周期
  4. react 事件机制
  5. react.Component 如何实现组件化以及高阶组件的应用
  6. setState 异步队列数据管理
  7. react Fiber 架构分析
  8. react hooks
  9. domdiff 算法
  10. snabbdom 源码,是怎样实现精简的 Virtual DOM
  11. redux单向数据流架构如何设计

VueReact 两个框架的粗略区别对比

Vue 的优点包括:css

  1. 模板和渲染函数的弹性选择
  2. 简单的语法及项目建立
  3. 更快的渲染速度和更小的体积

React 的优点包括:html

  1. 更适用于大型应用和更好的可测试性
  2. 同时适用于 Web 端和原生 App
  3. 更大的生态圈带来的更多支持和工具

类似之处

React 与 Vue 有不少类似之处,React 和 Vue 都是很是优秀的框架,它们之间的类似之处多过不一样之处,而且它们大部分最棒的功能是相通的:如他们都是 JavaScript 的 UI 框架,专一于创造前端的富应用。不一样于早期的 JavaScript 框架“功能齐全”,Reat 与 Vue 只有框架的骨架,其余的功能如路由、状态管理等是框架分离的组件。前端

  • 二者都是用于建立 UI 的 JavaScript 库;
  • 二者都快速轻便;
  • 都有基于组件的架构;
  • 都是用虚拟 DOM;
  • 均可放入单个 HTML 文件中,或者成为更复杂 webpack 设置中的模块;
  • 都有独立但经常使用的路由器和状态管理库;
  • 它们之间的最大区别是 Vue 一般使用 HTML 模板文件,而 React 则彻底是 JavaScript。Vue 有双向绑定语法糖。

不一样点

  • Vue 组件分为全局注册和局部注册,在 react 中都是经过 import 相应组件,而后模版中引用;
  • props 是能够动态变化的,子组件也实时更新,在 react 中官方建议 props 要像纯函数那样,输入输出一致对应,并且不太建议经过 props 来更改视图;
  • 子组件通常要显示地调用 props 选项来声明它期待得到的数据。而在 react 中没必要需,另二者都有 props 校验机制;
  • 每一个 Vue 实例都实现了事件接口,方便父子组件通讯,小型项目中不须要引入状态管理机制,而 react 必需本身实现;
  • 使用插槽分发内容,使得能够混合父组件的内容与子组件本身的模板;
  • 多了指令系统,让模版能够实现更丰富的功能,而 React 只能使用 JSX 语法;
  • Vue 增长的语法糖 computed 和 watch,而在 React 中须要本身写一套逻辑来实现;
  • react 的思路是 all in js,经过 js 来生成 html,因此设计了 jsx,还有经过 js 来操做 css,社区的 styled-component、jss 等;而 vue 是把 html,css,js 组合到一块儿,用各自的处理方式,vue 有单文件组件,能够把 html、css、js 写到一个文件中,html 提供了模板引擎来处理。
  • react 作的事情不多,不少都交给社区去作,vue 不少东西都是内置的,写起来确实方便一些, 好比 redux 的 combineReducer 就对应 vuex 的 modules, 好比 reselect 就对应 vuex 的 getter 和 vue 组件的 computed, vuex 的 mutation 是直接改变的原始数据,而 redux 的 reducer 是返回一个全新的 state,因此 redux 结合 immutable 来优化性能,vue 不须要。
  • react 是总体的思路的就是函数式,因此推崇纯组件,数据不可变,单向数据流,固然须要双向的地方也能够作到,好比结合 redux-form,组件的横向拆分通常是经过高阶组件。而 vue 是数据可变的,双向绑定,声明式的写法,vue 组件的横向拆分不少状况下用 mixin。

社区活跃度

从二者的 github 表现来看(数据取于 2019-09-16)vue

react

react

能够看出 vue 的 star 数量已是前端框架中最火爆的。从维护上来看,react 是 facebook 在维护,而 vue 现阶段虽然也有了团队,但主要仍是尤雨溪在维护贡献代码,而且阿里巴巴开源的混合式框架 weex 也是基于 vue 的,因此咱们相信 vue 将来将会获得更多的人和团队维护。node

根据不彻底统计,包括饿了么、简书、高德、稀土掘金、苏宁易购、美团、天猫、荔枝 FM、房多多、Laravel、htmlBurger 等国内外知名大公司都在使用 vue 进行新项目的开发和旧项目的前端重构工做。react

使用 React 的公司 facebook、Twitter、INS、Airbnb、Yahoo、ThoughtWorks、蚂蚁金服、阿里巴巴、腾讯、百度、口碑、美团、滴滴出行、饿了么、京东、网易等。webpack

UI 生态

vue react
pc 端 iview、element 等 Ant Design、Materal-UI 等
h5 端 有赞 vant、mintui 等 Ant Design Mobile、weui
混合开发 weexui、bui-weex teaset、react-native-elements
微信小程序 iview、Weapp、zanui iView Weapp、Taro UI

不管您选择React.js仍是Vue.js,两个框架都没有至关大的差别,根据您的要求,这个决定是很是主观的。若是您想将前端JavaScript框架集成到现有应用程序中,Vue.js是更好的选择,若是您想使用JavaScript构建移动应用程序,React绝对是您的选择。git

react16 版本常见 api

先来看一下 react 暴露出来的 APIgithub

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,

  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
  unstable_Profiler: REACT_PROFILER_TYPE,

  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
}
复制代码

Children

这个对象提供了一堆帮你处理 props.children 的方法,由于 children 是一个相似数组可是不是数组的数据结构,若是你要对其进行处理能够用 React.Children 外挂的方法。web

createRef

新的 ref 用法,React 即将抛弃<div ref="myDiv" />这种 string ref 的用法,未来你只能使用两种方式来使用 ref

class App extends React.Component {
  constructor() {
    this.ref = React.createRef()
  }

  render() {
    return <div ref={this.ref} />
    // or
    return <div ref={node => (this.funRef = node)} />
  }
}
复制代码

createContext

createContext 是官方定稿的 context 方案,在这以前咱们一直在用的老的 context API 都是 React 不推荐的 API,如今新的 API 释出,官方也已经肯定在 17 大版本会把老 API 去除(老 API 的性能不是通常的差)。

新 API 的使用方法:

const { Provider, Consumer } = React.createContext('defaultValue')

const ProviderComp = (props) => (
  <Provider value={'realValue'}> {props.children} </Provider>
)

const ConsumerComp = () => (
  <Consumer> {(value) => <p>{value}</p>} </Consumber>
)
复制代码

react 生命周期

目前 react 16.8 +的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段

  • 挂载阶段: constructor(props): 实例化。
    static getDerivedStateFromPropsprops 中获取 state
    render 渲染。
    componentDidMount: 完成挂载。
  • 更新阶段: static getDerivedStateFromProps 从 props 中获取 state。
    shouldComponentUpdate 判断是否须要重绘。
    render 渲染。
    getSnapshotBeforeUpdate 获取快照。
    componentDidUpdate 渲染完成后回调。
  • 卸载阶段: componentWillUnmount 即将卸载。
  • 错误处理: static getDerivedStateFromError 从错误中获取 state
    componentDidCatch 捕获错误并进行处理。
class ExampleComponent extends react.Component {
  // 构造函数,最早被执行,咱们一般在构造函数里初始化state对象或者给自定义方法绑定this
  constructor() {}
  //getDerivedStateFromProps(nextProps, prevState)用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
  // 这是个静态方法,当咱们接收到新的属性想去修改咱们state,可使用getDerivedStateFromProps
  static getDerivedStateFromProps(nextProps, prevState) {
    // 新的钩子 getDerivedStateFromProps() 更加纯粹, 它作的事情是将新传进来的属性和当前的状态值进行对比, 若不一致则更新当前的状态。
    if (nextProps.riderId !== prevState.riderId) {
      return {
        riderId: nextProps.riderId
      }
    }
    // 返回 null 则表示 state 不用做更新
    return null
  }
  // shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化以后的state,返回一个布尔值,true表示会触发从新渲染,false表示不会触发从新渲染,默认返回true,咱们一般利用今生命周期来优化react程序性能
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.id !== this.props.id
  }
  // 组件挂载后调用
  // 能够在该函数中进行请求或者订阅
  componentDidMount() {}
  // getSnapshotBeforeUpdate(prevProps, prevState):这个方法在render以后,componentDidUpdate以前调用,有两个参数prevProps和prevState,表示以前的属性和以前的state,这个函数有一个返回值,会做为第三个参数传给componentDidUpdate,若是你不想要返回值,能够返回null,今生命周期必须与componentDidUpdate搭配使用
  getSnapshotBeforeUpdate() {}
  // 组件即将销毁
  // 能够在此处移除订阅,定时器等等
  componentWillUnmount() {}
  // 组件销毁后调用
  componentDidUnMount() {}
  // componentDidUpdate(prevProps, prevState, snapshot):该方法在getSnapshotBeforeUpdate方法以后被调用,有三个参数prevProps,prevState,snapshot,表示以前的props,以前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,若是触发某些回调函数时须要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,而后在 componentDidUpdate 中统一触发回调或更新状态。
  componentDidUpdate() {}
  // 渲染组件函数
  render() {}
  // 如下函数不建议使用
  UNSAFE_componentWillMount() {}
  UNSAFE_componentWillUpdate(nextProps, nextState) {}
  UNSAFE_componentWillReceiveProps(nextProps) {}
}
复制代码

react 版本 17 将弃用几个类组件 API 生命周期:componentWillMountcomponentWillReceivePropscomponentWillUpdate

react 事件机制

简单的理解 react 如何处理事件的,React 在组件加载(mount)和更新(update)时,将事件经过 addEventListener 统一注册到 document 上,而后会有一个事件池存储了全部的事件,当事件触发的时候,经过 dispatchEvent 进行事件分发。

引用新手学习 react 迷惑的点(二)

  • react 里面绑定事件的方式和在 HTML 中绑定事件相似,使用驼峰式命名指定要绑定的 onClick 属性为组件定义的一个方法{this.handleClick.bind(this)}。
  • 因为类的方法默认不会绑定 this,所以在调用的时候若是忘记绑定,this 的值将会是 undefined。 一般若是不是直接调用,应该为方法绑定 this,将事件函数上下文绑定要组件实例上。

绑定事件的四种方式

class Button extends react.Component {
  constructor(props) {
    super(props)
    this.handleClick1 = this.handleClick1.bind(this)
  }
  //方式1:在构造函数中使用bind绑定this,官方推荐的绑定方式,也是性能最好的方式
  handleClick1() {
    console.log('this is:', this)
  }
  //方式2:在调用的时候使用bind绑定this
  handleClick2() {
    console.log('this is:', this)
  }
  //方式3:在调用的时候使用箭头函数绑定this
  // 方式2和方式3会有性能影响而且当方法做为属性传递给子组件的时候会引发重渲问题
  handleClick3() {
    console.log('this is:', this)
  }
  //方式4:使用属性初始化器语法绑定this,须要babel转义
  handleClick4 = () => {
    console.log('this is:', this)
  }
  render() {
    return (
      <div> <button onClick={this.handleClick1}>Click me</button> <button onClick={this.handleClick2.bind(this)}>Click me</button> <button onClick={() => this.handleClick3}>Click me</button> <button onClick={this.handleClick4}>Click me</button> </div>
    )
  }
}
复制代码

为何直接调用方法会报错

class Foo extends React.Component {
  handleClick() {
    this.setState({ xxx: aaa })
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Click me</button>
  }
}
复制代码

会被 babel 转化成

React.createElement(
  'button',
  {
    onClick: this.handleClick
  },
  'Click me'
)
复制代码

“合成事件”和“原生事件”

react 实现了一个“合成事件”层(synthetic event system),这抹平了各个浏览器的事件兼容性问题。全部事件均注册到了元素的最顶层-document 上,“合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,而且在组件卸载(unmount)的时候自动销毁绑定的事件。

react 组件开发

react 组件化思想

一个 UI 组件的完整模板
import classNames from 'classnames'
class Button extends react.Component {
  //参数传参与校验
  static propTypes = {
    type: PropTypes.oneOf(['success', 'normal']),
    onClick: PropTypes.func
  }
  static defaultProps = {
    type: 'normal'
  }
  handleClick() {}
  render() {
    let { className, type, children, ...other } = this.props
    const classes = classNames(
      className,
      'prefix-button',
      'prefix-button-' + type
    )
    return (
      <span className={classes} {...other} onClick={() => this.handleClick}> {children} </span>
    )
  }
}
复制代码

函数定义组件(Function Component)

纯展现型的,不须要维护 state 和生命周期,则优先使用 Function Component

  1. 代码更简洁,一看就知道是纯展现型的,没有复杂的业务逻辑
  2. 更好的复用性。只要传入相同结构的 props,就能展现相同的界面,不须要考虑反作用。
  3. 打包体积小,执行效率高
import react from 'react'
function MyComponent(props) {
  let { firstName, lastName } = props
  return (
    <div> <img src="avatar.png" className="profile" /> <h3>{[firstName, lastName].join(' ')}</h3> </div> ) } 复制代码

会被 babel 转义成

return React.createElement(
  'div',
  null,
  React.createElement('img', { src: 'avatar.png', className: 'profile' }),
  React.createElement('h3', null, [firstName, lastName].join(' '))
)
复制代码

那么,React.createElement 是在作什么?看下相关部分代码:

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  }
  // ...
  return element
}

ReactElement.createElement = function(type, config, children) {
  // ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  )
}
复制代码

React.createElement()来构建 React 元素的。它接受三个参数,第一个参数type能够是一个标签名。如 div、span,或者 React 组件。第二个参数props为传入的属性。第三个以及以后的参数children,皆做为组件的子组件。

createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps 对默认 props 进行赋值,而且对传入的孩子节点进行处理,最终构形成一个 reactElement 对象(所谓的虚拟 DOM)。 reactDOM.render 将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制而且对特定浏览器进行了性能优化,最终转换为真实 DOM。

ES6 class 定义一个纯组件(PureComponent

组件须要维护 state 或使用生命周期方法,则优先使用 PureComponent

class MyComponent extends react.Component {
  render() {
    let { name } = this.props
    return <h1>Hello, {name}</h1>
  }
}
复制代码
PureComponent

Component & PureComponent 这两个类基本相同,惟一的区别是 PureComponent 的原型上多了一个标识,shallowEqual(浅比较),来决定是否更新组件,浅比较相似于浅复制,只会比较第一层。使用 PureComponent 至关于省去了写 shouldComponentUpdate 函数

if (ctor.prototype && ctor.prototype.isPureReactComponent) {
  return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
}
复制代码

这是检查组件是否须要更新的一个判断,ctor 就是你声明的继承自 Component or PureComponent 的类,他会判断你是否继承自 PureComponent,若是是的话就 shallowEqual 比较 state 和 props。

React 中对比一个 ClassComponent 是否须要更新,只有两个地方。一是看有没有 shouldComponentUpdate 方法,二就是这里的 PureComponent 判断

使用不可变数据结构 Immutablejs

Immutable.js 是 Facebook 在 2014 年出的持久性数据结构的库,持久性指的是数据一旦建立,就不能再被更改,任何修改或添加删除操做都会返回一个新的 Immutable 对象。可让咱们更容易的去处理缓存、回退、数据变化检测等问题,简化开发。而且提供了大量的相似原生 JS 的方法,还有 Lazy Operation 的特性,彻底的函数式编程。

import { Map } from 'immutable'
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1 !== map2 // true
map1.get('b') // 2
map2.get('b') // 50
map1.get('a') === map2.get('a') // true
复制代码

能够看到,修改 map1 的属性返回 map2,他们并非指向同一存储空间,map1 声明了只有,全部的操做都不会改变它。

ImmutableJS提供了大量的方法去更新、删除、添加数据,极大的方便了咱们操纵数据。除此以外,还提供了原生类型与 ImmutableJS 类型判断与转换方法:

import { fromJS, isImmutable } from 'immutable'
const obj = fromJS({
  a: 'test',
  b: [1, 2, 4]
}) // 支持混合类型
isImmutable(obj) // true
obj.size() // 2
const obj1 = obj.toJS() // 转换成原生 `js` 类型
复制代码

ImmutableJS 最大的两个特性就是: immutable data structures(持久性数据结构)与 structural sharing(结构共享),持久性数据结构保证数据一旦建立就不能修改,使用旧数据建立新数据时,旧数据也不会改变,不会像原生 js 那样新数据的操做会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减小了深拷贝的性能消耗,也减小了内存。

好比下图:

react-tree

左边是旧值,右边是新值,我须要改变左边红色节点的值,生成的新值改变了红色节点到根节点路径之间的全部节点,也就是全部青色节点的值,旧值没有任何改变,其余使用它的地方并不会受影响,而超过一大半的蓝色节点仍是和旧值共享的。在 ImmutableJS 内部,构造了一种特殊的数据结构,把原生的值结合一系列的私有属性,建立成 ImmutableJS 类型,每次改变值,先会经过私有属性的辅助检测,而后改变对应的须要改变的私有属性和真实值,最后生成一个新的值,中间会有不少的优化,因此性能会很高。

高阶组件(higher order component)

高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, ...props } = this.props
      if (visible === false) return null
      return <WrappedComponent {...props} /> } } } 复制代码

上面的代码就是一个 HOC 的简单应用,函数接收一个组件做为参数,并返回一个新组件,新组建能够接收一个 visible props,根据 visible 的值来判断是否渲染 Visible。 最多见的还有 Redux 的 connect 函数。除了简单分享工具库和简单的组合,HOC 最好的方式是共享 react 组件之间的行为。若是你发现你在不一样的地方写了大量代码来作同一件事时,就应该考虑将代码重构为可重用的 HOC。 下面就是一个简化版的 connect 实现:

export const connect = (
  mapStateToProps,
  mapDispatchToProps
) => WrappedComponent => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor() {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount() {
      const { store } = this.context
      this._updateProps()
      store.subscribe(() => this._updateProps())
    }

    _updateProps() {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props)
        : {}
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props)
        : {}
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render() {
      return <WrappedComponent {...this.state.allProps} /> } } return Connect } 复制代码

代码很是清晰,connect 函数其实就作了一件事,将 mapStateToPropsmapDispatchToProps 分别解构后传给原组件,这样咱们在原组件内就能够直接用 props 获取 state 以及 dispatch 函数了。

高阶组件的应用

某些页面须要记录用户行为,性能指标等等,经过高阶组件作这些事情能够省去不少重复代码。

日志打点
function logHoc(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      this.start = Date.now()
    }
    componentDidMount() {
      this.end = Date.now()
      console.log(
        `${WrappedComponent.dispalyName} 渲染时间:${this.end - this.start} ms`
      )
      console.log(`${user}进入${WrappedComponent.dispalyName}`)
    }
    componentWillUnmount() {
      console.log(`${user}退出${WrappedComponent.dispalyName}`)
    }
    render() {
      return <WrappedComponent {...this.props} /> } } } 复制代码
可用、权限控制
function auth(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, auth, display = null, ...props } = this.props
      if (visible === false || (auth && authList.indexOf(auth) === -1)) {
        return display
      }
      return <WrappedComponent {...props} /> } } } 复制代码
表单校验

基于上面的双向绑定的例子,咱们再来一个表单验证器,表单验证器能够包含验证函数以及提示信息,当验证不经过时,展现错误信息:

function validateHoc(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props)
      this.state = { error: '' }
    }
    onChange = event => {
      const { validator } = this.props
      if (validator && typeof validator.func === 'function') {
        if (!validator.func(event.target.value)) {
          this.setState({ error: validator.msg })
        } else {
          this.setState({ error: '' })
        }
      }
    }
    render() {
      return (
        <div> <WrappedComponent onChange={this.onChange} {...this.props} /> <div>{this.state.error || ''}</div> </div> ) } } } 复制代码
const validatorName = {
  func: (val) => val && !isNaN(val),
  msg: '请输入数字'
}
const validatorPwd = {
  func: (val) => val && val.length > 6,
  msg: '密码必须大于6位'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>
复制代码

HOC 的缺陷

  • HOC 须要在原组件上进行包裹或者嵌套,若是大量使用 HOC,将会产生很是多的嵌套,这让调试变得很是困难。
  • HOC 能够劫持 props,在不遵照约定的状况下也可能形成冲突。

render props

一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术 具备 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现本身的渲染逻辑。

<DataProvider render={data => <h1>Hello {data.target}</h1>} />
复制代码

setState 数据管理

**不要直接更新状态**
// Wrong 此代码不会从新渲染组件,构造函数是惟一可以初始化 this.state 的地方。
this.state.comment = 'Hello'
// Correct 应当使用 setState():
this.setState({ comment: 'Hello' })
复制代码
组件生命周期中或者 react 事件绑定中,setState 是经过异步更新的,在延时的回调或者原生事件绑定的回调中调用 setState 不必定是异步的。
  • 多个 setState() 调用合并成一个调用来提升性能。
  • this.props 和 this.state 多是异步更新的,不该该依靠它们的值来计算下一个状态。
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment
})
// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}))
复制代码
原生事件绑定不会经过合成事件的方式处理,会进入更新事务的处理流程。`setTimeout` 也同样,在 `setTimeout` 回调执行时已经完成了原更新组件流程,不会放入 `dirtyComponent` 进行异步更新,其结果天然是同步的。

setState 原理

setState 并无直接操做去渲染,而是执行了一个 updateQueue(异步 updater 队列),

setState( stateChange ) {
    Object.assign( this.state, stateChange );
    //合并接收到的state||stateChange改变的state(setState接收到的参数)
    renderComponent( this );//调用render渲染组件
}
复制代码

这种实现,每次调用 setState 都会更新 state 并立刻渲染一次(不符合其更新优化机制),因此咱们要合并 setState

具体能够阅读源码 ReactUpdateQueue.js

react 中的事务实现

待完善 这块看的还有点懵圈 React 源码解析(三):详解事务与更新队列 React 中的 Transaction React 的事务机制

transaction 事务

ErrorBoundarySuspenseFragment

Error Boundaries

react 16 提供了一个新的错误捕获钩子 componentDidCatch(error, errorInfo), 它能将子组件生命周期里所抛出的错误捕获, 防止页面全局崩溃。demo componentDidCatch 并不会捕获如下几种错误

  • 事件机制抛出的错误(事件里的错误并不会影响渲染)
  • Error Boundaries 自身抛出的错误
  • 异步产生的错误
  • 服务端渲染

lazy、Suspence 延迟加载组件

lazy 须要跟 Suspence 配合使用,不然会报错。

lazy 其实是帮助咱们实现代码分割 ,相似 webpack 的 splitchunk 的功能。

Suspense 意思是能暂停当前组件的渲染, 当完成某件事之后再继续渲染。简单来讲就是减小首屏代码的体积,提高性能。

import react, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
  return (
    <Suspense fallback={<div>loading...</div>}> <OtherComponent /> </Suspense>
  )
}
复制代码

一种简单的预加载思路, 可参考 preload

const OtherComponentPromise = import('./OtherComponent')
const OtherComponent = react.lazy(() => OtherComponentPromise)
复制代码

Fragments(v16.2.0)

Fragments 容许你将子列表分组,避免向 DOM 添加额外的节点。

render() {
  return (
    <> <ChildA /> <ChildB /> <ChildC /> </> ); } 复制代码

react Fiber 架构分析

react-fiber是为了加强动画、布局、移动端手势领域的适用性,最重要的特性是对页面渲染的优化: 容许将渲染方面的工做拆分为多段进行。

react Fiber 架构解决了什么问题

react-fiber 能够为咱们提供以下几个功能:

  • 设置渲染任务的优先
  • 采用新的 Diff 算法
  • 采用虚拟栈设计容许当优先级更高的渲染任务和较低优先的任务之间来回切换

Fiber 如何作到异步渲染 Virtual DomDiff 算法

众所周知,画面每秒钟更新 60 次,页面在人眼中显得流畅,无明显卡顿。每秒 60 次,即 16ms 要更新一次页面,若是更新页面消耗的时间不到 16ms,那么在下一次更新时机来到以前会剩下一点时间执行其余的任务,只要保证及时在 16ms 的间隔下更新界面就彻底不会影响到页面的流畅程度。fiber 的核心正是利用了 60 帧原则,实现了一个基于优先级和 requestIdleCallback 的循环任务调度算法。

function fiber(剩余时间) {
  if (剩余时间 > 任务所需时间) {
    作任务
  } else {
    requestIdleCallback(fiber)
    // requestIdleCallback 是浏览器提供的一个 api,可让浏览器在空闲的时候执行回调,
    // 在回调参数中能够获取到当前帧剩余的时间,fiber 利用了这个参数,
    // 判断当前剩下的时间是否足够继续执行任务,
    // 若是足够则继续执行,不然暂停任务,
    // 并调用 requestIdleCallback 通知浏览器空闲的时候继续执行当前的任务
  }
}
复制代码

react hooks

在 react 16.7 以前, react 有两种形式的组件, 有状态组件(类)和无状态组件(函数)。 官方解释: hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。 我的理解:让传统的函数组件 function component 有内部状态 state 的函数 function,简单来讲就是 hooks 让函数组件有了状态,能够彻底替代 class。

接下来梳理 Hooks 中最核心的 2 个 api, useStateuseEffect

useState

useState 是一个钩子,他能够为函数式组件增长一些状态,而且提供改变这些状态的函数,同时它接收一个参数,这个参数做为状态的默认值。

const [count, setCount] = useState(initialState)
复制代码

使用 Hooks 相比以前用 class 的写法最直观的感觉是更为简洁

function App() {
  const [count, setCount] = useState(0)
  return (
    <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
  )
}
复制代码

useEffect(fn)

在每次 render 后都会执行这个钩子。能够将它当成是 componentDidMountcomponentDidUpdate``、componentWillUnmount 的合集。所以使用 useEffect 比以前优越的地方在于:

能够避免在 componentDidMountcomponentDidUpdate 书写重复的代码; 能够将关联逻辑写进一个 useEffect(在之前得写进不一样生命周期里);

深刻理解 react 原理

react 虚拟 dom 原理剖析

react 组件的渲染流程

使用 react.createElement 或 JSX 编写 react 组件,实际上全部的 JSX 代码最后都会转换成 react.createElement(...),Babel 帮助咱们完成了这个转换的过程。

createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps 对默认 props 进行赋值,而且对传入的孩子节点进行处理,最终构形成一个 reactElement 对象(所谓的虚拟 DOM)。

reactDOM.render 将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制而且对特定浏览器进行了性能优化,最终转换为真实 DOM。

虚拟 DOM 的组成

reactElementelement 对象,咱们的组件最终会被渲染成下面的结构:

`type`:元素的类型,能够是原生 html 类型(字符串),或者自定义组件(函数或 class)
`key`:组件的惟一标识,用于 Diff 算法,下面会详细介绍
`ref`:用于访问原生 dom 节点
`props`:传入组件的 props,chidren 是 props 中的一个属性,它存储了当前组件的孩子节点,能够是数组(多个孩子节点)或对象(只有一个孩子节点)
`owner`:当前正在构建的 Component 所属的 Component
`self`:(非生产环境)指定当前位于哪一个组件实例
`_source`:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)
复制代码

当组件状态 state 有更改的时候,react 会自动调用组件的 render 方法从新渲染整个组件的 UI。 固然若是真的这样大面积的操做 DOM,性能会是一个很大的问题,因此 react 实现了一个 Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM上,react 在这个 Virtual DOM 上实现了一个 diff 算法,当要从新渲染组件的时候,会经过 diff 寻找到要变动的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,因此实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,因此性能会比原生 DOM 快不少。

react 是如何防止 XSS

reactElement 对象还有一个$$typeof属性,它是一个 Symbol 类型的变量Symbol.for('react.element'),当环境不支持 Symbol 时,$$typeof 被赋值为 0xeac7。 这个变量能够防止 XSS。若是你的服务器有一个漏洞,容许用户存储任意 JSON 对象, 而客户端代码须要一个字符串,这可能为你的应用程序带来风险。JSON 中不能存储 Symbol 类型的变量,而 react 渲染时会把没有\$\$typeof 标识的组件过滤掉。

diff 算法

传统的 diff 算法经过循环递归对节点一次对比,效率很低,算法复杂度达到 O(n^3),其中 n 是树中节点的总数,React 经过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。

diff 策略:

  1. web ui 中 Dom 节点跨层级的移动操做不多,diff 算法比较新旧节点的时候,比较只会在同层级比较,不会跨层级比较
  2. 拥有相同类的两个组件将会生成类似的树形结构,拥有不一样类的两个组件将会生成不一样的树形结构。
  3. 对于同一层级的一组子节点,他们能够经过惟一 key 进行区分

基于以上三个前提策略,React 分别对 tree diffcomponent diff 以及 element diff 进行算法优化,事实也证实这三个前提策略是合理且准确的,它保证了总体界面构建的性能。 简单的讲就是:

具体能够参考React 源码剖析系列 - 难以想象的 react diff

  • React 经过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 经过分层求异的策略,对 tree diff 进行算法优化;
  • React 经过相同类生成类似树形结构,不一样类生成不一样树形结构的策略,对 component diff 进行算法优化;
  • React 经过设置惟一 key 的策略,对 element diff 进行算法优化;

建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提高; 建议,在开发过程当中,尽可能减小相似将最后一个节点移动到列表首部的操做,当节点数量过大或更新操做过于频繁时,在必定程度上会影响 React 的渲染性能。

snabbdom 源码,是怎样实现精简的 Virtual DOM 的

待补充

react 性能分析与优化

减小没必要要的渲染

在使用 class Component 进行开发的时候,咱们可使用 shouldComponentUpdate 来减小没必要要的渲染,那么在使用 react hooks 后,咱们如何实现这样的功能呢?

解决方案:React.memouseMemo 对于这种状况,react 固然也给出了官方的解决方案,就是使用 React.memo 和 useMemo。

React.memo

React.momo 其实并非一个 hook,它其实等价于 PureComponent,可是它只会对比 props。使用方式以下(用上面的例子):

import React, { useState } from 'react'

export const Count = React.memo(props => {
  const [data, setData] = useState({
    count: 0,
    name: 'cjg',
    age: 18
  })

  const handleClick = () => {
    const { count } = data
    setData({
      ...data,
      count: count + 1
    })
  }

  return <button onClick={handleClick}>count:{data.count}</button>
})
复制代码
useMemo

useMemo 它的用法其实跟 useEffects 有点像,咱们直接看官方给的例子

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a])
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b])
  return (
    <> {child1} {child2} </> ) } 复制代码

从例子能够看出来,它的第二个参数和 useEffect 的第二个参数是同样的,只有在第二个参数数组的值发生变化时,才会触发子组件的更新。

引用React hooks 实践

使用 shouldComponentUpdate() 防止没必要要的从新渲染

当一个组件的 props 或 state 变动,React 会将最新返回的元素与以前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。

即便 React 只更新改变了的 DOM 节点,从新渲染仍然花费了一些时间。在大部分状况下它并非问题,可是若是渲染的组件很是多时,就会浮现性能上的问题,咱们能够经过覆盖生命周期方法 shouldComponentUpdate 来进行提速。

shouldComponentUpdate 方法会在从新渲染前被触发。其默认实现老是返回 true,若是组件不须要更新,能够在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及以后的操做。

shouldComponentUpdate(nextProps, nextState) {
   return nextProps.next !== this.props.next
}
复制代码

React 性能分析器

React 16.5 增长了对新的开发者工具 DevTools 性能分析插件的支持。 此插件使用 React 实验性的 Profiler API 来收集有关每一个组件渲染的用时信息,以便识别 React 应用程序中的性能瓶颈。 它将与咱们即将推出的 time slicing(时间分片) 和 suspense(悬停) 功能彻底兼容。

redux

Store:保存数据的地方,你能够把它当作一个容器,整个应用只能有一个 Store

StateStore 对象包含全部数据,若是想获得某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫作 State

ActionState 的变化,会致使 View 的变化。可是,用户接触不到 State,只能接触到 View。因此,State 的变化必须是 View 致使的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action Creator:View 要发送多少种消息,就会有多少种 Action。若是都手写,会很麻烦,因此咱们定义一个函数来生成 Action,这个函数就叫 Action Creator

ReducerStore 收到 Action 之后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫作 ReducerReducer 是一个函数,它接受 Action 和当前 State 做为参数,返回一个新的 State

dispatch:是 View 发出 Action 的惟一方法。

redux 的基本原理

而后咱们过下整个工做流程:

首先,用户(经过 View)发出 Action,发出方式就用到了 dispatch 方法。

而后,Store 自动调用 Reducer,而且传入两个参数:当前 State 和收到的 ActionReducer 会返回新的 State

State 一旦有变化,Store 就会调用监听函数,来更新 View

到这儿为止,一次用户交互流程结束。能够看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。

redux 单向数据流架构如何设计

待完善

redux 中间件

Redux 的中间件提供的是位于 action 被发起以后,到达 reducer 以前的扩展点,换而言之,本来 view -> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节咱们能够作一些 “反作用” 的操做,如 异步请求、打印日志等。

redux 中间件经过改写 store.dispatch 方法实现了 action -> reducer 的拦截,从上面的描述中能够更加清晰地理解 redux 中间件的洋葱圈模型:

中间件A -> 中间件B-> 中间件C-> 原始 dispatch -> 中间件C -> 中间件B ->  中间件A
复制代码

这也就提醒咱们使用中间件时须要注意这个中间件是在何时 “搞事情” 的,好比 redux-thunk 在执行 next(action) 前就拦截了类型为 function 的 action,而 redux-saga 就在 next(action) 才会触发监听 sagaEmitter.emit(action), 并不会拦截已有 action 到达 reducer。

参考:

  1. 深刻分析虚拟 DOM 的渲染原理和特性
  2. react 事件机制
  3. 从 Mixin 到 HOC 再到 Hook
  4. 美团技术团队-Redux 从设计到源码
  5. 解析 snabbdom 源码,教你实现精简的 Virtual DOM 库
  6. react 源码解析
  7. Vue 与 React 两个框架的粗略区别对比
相关文章
相关标签/搜索