react新手指南:一篇文章读懂react 目录

0.更新日志

2017.11.06 第一版

1.React解决了什么问题?

1.1 React实现了Virtual DOM

在一定程度上提升了性能,尤其是在进行小量数据更新时。因为DOM操作是很耗性能的,而Virtual DOM是在内存中进行操作的,当数据发生变化时,通过diff算法比较两棵树之间的变化,再进行必要的DOM更新,省去了不必要的高消耗的DOM操作。当然,这种性能优化主要体现在有小量数据更新的情况下。因为React的基本思维模式是每次有变动就重新渲染整个应用,简单想来就是直接重置innerHTML,比如说在一个大型列表所有数据都变动的情况下,重置innerHTML还比较合理,但若是只有一行数据变了,它也需要重置整个innerHTML,就会造成大量的浪费。而Virtual DOM虽然进行了JS层面的计算,但是比起DOM操作来说,简直不要太便宜。

1.2 React的一个核心思想是声明式编程。

命令式编程是解决做什么的问题,就像是下命令一样,关注于怎么做,而声明式编程关注于得到什么结果,在React中,我们只需要关注“目前的状态是什么”,而不是“我需要做什么让页面变成目前的状态”。React就是不断声明,然后在特定的参数下渲染UI界面。这种编程方式可以让我们的代码更容易被理解,从而易于维护。

1.3 组件化

React天生组件化,我们可以将一个大的应用分割成很多小组件,这样有好几个优势。首先组件化的代码像一棵树一样清楚干净,比起传统的面条式代码可读性更高;其次前端人员在开发过程中可以并行开发组件而不影响,大大提高了开发效率;最重要的是,组件化使得复用性大大提高,团队可以沉淀一些公共组件或工具库。

1.4 单向数据流

在React中数据流是单向的,由父节点流向子节点,如果父节点的props发生了变化,那么React会递归遍历整个组件树,重新渲染所有使用该属性的子组件。这种单向的数据流一方面比较清晰不容易混乱,另一方面是比较好维护,出了问题也比较好定位。

2.react的概念要点

2.1 组件的生命周期

盗图两张:

react组件生命周期

react父子组件关系

组件生命周期有三种阶段:初始化阶段(Mounting)、更新阶段(Updating)、析构阶段(Unmouting)。

初始化阶段:

  • constructor():初始化state、绑定事件
  • componentWillMount():在render()之前执行,除了同构,跟constructor没啥差别
  • render():用于渲染DOM。如果有操作DOM或和浏览器打交道的操作,最好在下一个步骤执行。
  • componentDidMount():在render()之后立即执行,可以在这个函数中对DOM就进行操作,可以加载服务器数据,可以使用setState()方法触发重新渲染

组件更新阶段:

  • componentWillReceiveProps(nextProps):在已挂载的组件接收到新props时触发,传进来的props没有变化也可能触发该函数,若需要实现props变化才执行操作的话需要自己手动判断
  • componentShouldUpdate(nextProps,nextState):默认返回true,我们可以手动判断需不需要触发render,若返回false,就不触发下一步骤
  • componentWillUpdate():componentShouldUpdate返回true时触发,在render之前,可以在里面进行操作DOM
  • render():重渲染
  • componentDidUpdate():render之后立即触发

组件卸载阶段:

  • componentWillUnmount():在组件销毁之前触发,可以处理一些清理操作,如无效的timers等
  • componentDidMount():卸载后立即触发

2.2 组件的render函数何时被调用

  • 组件state发生改变时会调用render函数,比如通过setState函数改变组件自身的state值
  • 继承的props属性发生改变时也会调用render函数,即使改变的前后值一样
  • React生命周期中有个componentShouldUpdate函数,默认返回true,即允许render被调用,我们也可以重写这个函数,判断是否应该调用render函数

2.3 render时DOM就一定会被更新吗

React组件中存在两类DOM,render函数被调用后, React会根据props或者state重新创建一棵virtual DOM树,虽然每一次调用都重新创建,但因为创建是发生在内存中,所以很快不影响性能。而 virtual dom的更新并不意味着真实DOM的更新,React采用diff算法将virtual DOM和真实DOM进行比较,找出需要更新的最小的部分,这时Real DOM才可能发生修改。

所以每次state的更改都会使得render函数被调用,但是页面DOM不一定发生修改

2.4 state的相关问题

2.4.1 远程数据加载时,应该在哪个周期中完成

最好是在componentDidMount中进行异步请求。如果我们将ajax请求放在生命周期其他函数中,如constructor或componentWIllMount中,我们并不能保证请求仅在组件挂载完毕后才响应。如果我们的数据请求在组件挂载前就完成,并调用setState函数将数据添加到组件状态中,对于未挂载的组件会报错。而在componentDidMount中进行ajax请求能有效避免这个问题。

React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。

顺便说说componentWillMount函数,这个方法是在render前立刻执行的,也是服务器渲染中唯一调用的钩子,其实除了同构的需求,通常情况下可以用constructor()方法代替。

2.4.2 在哪些生命周期中可以修改组件的state

  • componentDidMount和componentDidUpdate
  • constructor、componentWillMount中setState会发生错误:setState只能在mounted或mounting组件中执行
  • componentWillUpdate中setState会导致死循环

2.4.3 state里应该有什么

应该有啥:

  • 事件函数可能进行修改的会导致UI进行渲染的数据

不应该有啥:

  • 计算得出的值
  • React组件
  • props复制来的数据

2.4.4 调用setState时,发生了什么事

调用setState时,react会做的第一件事就是将传递给setState的对象合并到组件的当前状态,然后会触发调和过程。经过调和过程,React会以相对高效的方式根据新的状态构建React元素树,并准备重新渲染整个UI界面。在React得到元素树后,React会通过diff算法算出新的树与老树之间的节点差异,然后根据差异对界面进行最小化重渲染。在diff算法中,React能够相对精确地算出哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

2.4.5 不同父节点的组件需要对彼此的状态进行改变时应该怎么实现

Redux

2.5 事件在React中的处理方式

React在virtual DOM的基础上实现了一个SyntheticEvent(合成事件)层,所有事件都绑定到最外层上。

在React底层,主要对合成事件做了两件事:事件委托和自动绑定。

事件委托:React的事件代理机制不会把事件处理函数直接绑定到真实的结点上,而是把所有事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。

自动绑定:在React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。在使用ES6 classes和纯函数时,这种自动绑定就不存在了,需要我们手动绑定this:bind方法、双冒号语法、构造器内声明、箭头函数。

3.react技巧

3.1 如何设计一个好组件

组件的主要目的是为了更好的复用,所以在设计组件的时候需要遵循高内聚低耦合的原则。

  • 可以通过遵循几种设计模式原则来达到高复用的目的,比如单一职责原则:React推崇的是“组合”而非“继承”,所以在设计时尽量不设计大的组件,而是开发若干个单一功能的组件,重点就是每个组件只做一件事;开放/封闭原则,就是常说的对修改封闭,对扩展开放。在React中我们可以用高阶组件来实现。
  • 使用高阶组件来实现组件的复用。高阶组件就是一个包装了另一个React组件的React组件,它包括属性代理(高阶组件操控着传递给被包裹组件的属性)和反向继承(实际上高阶组件继承被包裹组件)。我们可以用高阶组件实现代码复用,逻辑抽象。
  • 使用容器组件来处理逻辑,展示组件来展示数据(也就是逻辑处理与数据展示分离)。比如可以在容器组件中进行数据的请求与处理,然后将处理后的数据传递给展示组件,展示组件只负责展示,这样容器组件和展示组件就可以更好地复用了。
  • 编写组件代码时要符合规范,总之就是要可读性强、复用性高、可维护性好。