对React、Redux、React-Redux详细剖析

前言

相信很多新手朋友们对于React、Redux、React-Redux这三者之间的关系和区别肯定有很多不解和疑惑。这里我们就来详细的剖析一下它们吧。

React:负责组件的UI界面渲染;

Redux:数据处理中心;

React-Redux:连接组件和数据中心,也就是把React和Redux联系起来。

React

React主要就是用来实现UI界面的,是一个专注于view层的框架。对于一些小项目,如果数据的交互不是很多,完全可以只使用React就能很好的实现。

在传统的页面开发模式中,需要多次的操作DOM来进行页面的更新,我们都知道对DOM的操作会造成极大的性能问题。而React的提出就是减少对DOM的操作来提升性能,也就是Virtual DOM。

Virtual DOM

Virtual DOM就相当于一个虚拟空间,React就是基于 Virtual DOM 来工作的。

它的工作过程是:当有数据需要进行更新时,会先计算 Virtual DOM ,并和上一次的 Virtual DOM 做对比,得到DOM结构的区别,然后只会将需要变化的部分批量的更新到真实的DOM上。

说到如何去计算Virtual DOM,在React里面,用到的是react-diff算法。我们都知道传统的diff算法是通过循环递归对每个节点进行依次对比,效率低下,算法复杂度达到了 O(n^3),其中 n 是树中节点的总数。

根据react diff策略:

  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计;
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;
  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

React 分别对 tree diff、component diff 以及 element diff 进行了算法优化:

  1. tree diff:对树进行分层比较,两棵树只会对同一层次的节点进行比较
  2. component diff
  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
  1. element diff:当节点处于同一层级时,React diff 提供了三种节点操作,分别为:插入、移动、删除。

这是整个react diff算法的比较流程图:

react diff

React生命周期

React总共有10个周期函数(render重复一次),这10个函数可以满足我们所有对组件操作的需求,利用的好可以提高开发效率和组件性能。

一、组件在初始化时会触发5个钩子函数:

  1. getDefaultProps()

设置默认的props,es6中用 static dufaultProps={} 设置组件的默认属性。在整个生命周期只执行一次。

  1. getInitialState()

在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props。

  1. componentWillMount() ajax数据的拉取操作,定时器的启动。

组件初始化时调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。

  1. render()

React最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。

  1. componentDidMount() 动画的启动,输入框自动聚焦

组件渲染之后调用,可以通过this.getDOMNode()获取和操作dom节点,只调用一次。

二、在更新时也会触发5个钩子函数:

  1. componentWillReceivePorps(nextProps)

组件初始化时不调用,组件接受新的props时调用。不管父组件传递给子组件的props有没有改变,都会触发。

  1. shouldComponentUpdate(nextProps, nextState)

React性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候。不过调用this.forceUpdate会跳过此步骤。

  1. componentWillUpdate(nextProps, nextState)

组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state

  1. render()

不多说

  1. componentDidUpdate()

组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。

三、卸载钩子函数

  1. componentWillUnmount() 定时器的清除

组件将要卸载时调用,一些事件监听和定时器需要在此时清除。

Redux

Redux是一种架构模式,是由flux发展而来的。

Redux三大原则

  1. 唯一数据源
  2. 状态只读
  3. 数据改变只能通过纯函数(reducer)完成

Redux核心api

Redux主要由三部分组成:store,reducer,action。

store

Redux的核心是store,它由Redux提供的 createStore(reducer, defaultState) 这个方法生成,生成三个方法,getState(),dispatch(),subscrible()。

store

  • getState():存储的数据,状态树;
  • dispatch(action):分发action,并返回一个action,这是唯一能改变store中数据的方式;
  • subscrible(listener):注册一个监听者,store发生变化的时候被调用。

reducer

reducer是一个纯函数,它根据previousState和action计算出新的state。

reducer(previousState,action)
reducer

action

action本质上是一个JavaScript对象,其中必须包含一个type字段来表示将要执行的动作,其他的字段都可以根据需求来自定义。

1
const ADD_TODO = 'ADD_TODO'

1
2
3
4
{
type: ADD_TODO,
text: 'Build my first Redux app'
}

整合

他们三者之间的交互,可以由下图概括:

React-Redux

Redux 本身和React没有关系,只是数据处理中心,是React-Redux让他们联系在一起。

React-rRedux提供两个方法:connect和Provider。

connect

connect连接React组件和Redux store。connect实际上是一个高阶函数,返回一个新的已与 Redux store 连接的组件类。

1
2
3
4
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

TodoList是 UI 组件,VisibleTodoList就是由 react-redux 通过connect方法自动生成的容器组件。

  1. mapStateToProps:从Redux状态树中提取需要的部分作为props传递给当前的组件。
  2. mapDispatchToProps:将需要绑定的响应事件(action)作为props传递到组件上。

Provider

Provider实现store的全局访问,将store传给每个组件。

原理:使用React的context,context可以实现跨组件之间的传递。

总结

下图阐述了它们三者之间的工作流程: