React

225 阅读7分钟

合成事件

动机:React为了避免DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent

机制:React并不是将click事件直接绑定在dom上面,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给正式的函数

  • (1).React并不是将click事件直接绑定在dom上面;
  • (2).而是采用事件冒泡的形式冒泡到document上面,然后将事件内容封装交给中间层SyntheticEvent(负责所有事件合成);
  • (3).所以当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行。

执行顺序:原生事件 -> React合成事件

React中不建议使用原生事件,原生事件中如果执行stopPropagation方法,则会导致其他 React事件失效。

因为所有元素的事件将无法冒泡到 document上,导致所有的React事件都将无法被触发。

原生事件和React事件的区别:

  • React 事件使用驼峰命名,而不是全部小写;
  • 通过JSX传递一个函数作为事件处理程序,而不是一个字符串;
  • 在 React 中你不能通过返回 false 来阻止默认行为,必须明确调用 preventDefault。

setState

(1)机制:在 React的生命周期和合成事件中,React仍然处于他的更新机制中,这时无论调用多少次 setState,都会不会立即执行更新,而是将要更新的·存入 _pendingStateQueue,将要更新的组件存入dirtyComponent。当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件 didmount后会将批处理标志设置为 false。 这时将取出 dirtyComponent中的组件以及_pendingStateQueue中的state进行更新。这样就可以确保组件不会被重新渲染多次。

(2)setState并不是异步的,而是React的批处理机制给人一种异步的假象。

(3)在原生事件中调用 setState并不会出发React的批处理机制,所以立即能拿到最新结果。

(4)setState的第二个参数接收一个函数,该函数会在React的批处理机制完成之后调用,所以你想在调用 setState后立即获取更新后的值,请在该回调函数中获取。

(5)React会对多次连续的setState进行合并,如果想立即使用上次 setState后的结果进行下一次 setState,可以让 setState 接收一个函数而不是一个对象

this.setState((state, props) => {index: this.state.index+1}, () => {console.log(this.state.index)})

4.虚拟DOM

React会先将代码转换成一个JavaScript对象(虚拟DOM),然后这个JavaScript对象再转换成真实 DOM。

React diff 会帮助我们计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行实际 DOM 操作,而非重新渲染整个页面,从而保证了每次操作更新后页面的高效渲染

同级比较:首先会比较最顶层的虚拟DOM节点是否一致,如果一致的话,就继续比较下一层的节点;如果不一致的话,react就会把这个节点及其下面所有节点全部删掉,重新生成一遍新的DOM,然后用新的DOM替换原始页面的DOM。

render() -> 虚拟dom

ReactDOM.render() -> 真实dom

因为改变某一dom会导致所有子类重新渲染,所以尽量不要操作dom,可以通过css显示隐藏。

React的性能消耗模型也很容易理解:每一次setState都会重新渲染所有子树。尽量减少setState的使用频率,并且使用shouldComponentUpdate(nextProps,nextState)来阻止大型子树的重新渲染。

VitrualDom中?typeof:是一个 Symbol类型的变量,可以防止 XSS

React组件的渲染流程

(1)JSX编写的组件会被转换成React.createElement(...);

(2)createElement函数对 key和 ref等特殊的 props进行处理,并获取defaultProps对默认 props进行赋值,且对子节点进行处理,最终构造成一个虚拟 DOM;

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

React组件首字母必须大写

babel在编译时会判断JSX中组件的首字母,当首字母为小写时,其被认定为原生 DOM标签

为什么不能用数组下标来作为react组件中的key?

react 使用diff算法,使用key来做同级比对。如果使用数组下标作为key,有以下情况: 在数组头部或中部插入或删除元素:所有key对应的节点的值发生更改,进行重新渲染,造成性能损耗。 而如果使用数组中唯一值来作为key:不管是在何处插入或删除节点,其他key对应的节点的值未发生更改,只需插入或删除操作的数组节点。

shouldComponentUpdate和React.PureComponent

  • shouldComponentUpdate这个函数会在组件重新渲染之前调用,函数的返回值确定了组件是否需要重新渲染。函数默认的返回值是true,意思就是只要组件的 props 或者 state 发生了变化,就会重新构建 virtual DOM,然后使用 diff算法进行比较,再接着根据比较结果决定是否重新渲染整个组件;
shouldComponentUpdate(nextProps, nextState) {
  return nextState.someData !== this.state.someData
}
  • React.PureComponent自动的帮我们编写shouldComponentUpdate方法, 避免我们为每个组件都编写一次的麻烦;
  • React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过props和state的浅对比来实现shouldComponentUpate()。如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断,表现为对象深层的数据已改变视图却没有更新

react 性能优化?

代码层面:

  • 使用return null而不是CSS的display:none来控制节点的显示隐藏。保证同一时间页面的DOM节点尽可能的少;
  • 不要使用数组下标作为key;
  • props和state的数据尽可能简单明了;
  • 利用 shouldComponentUpdate 和 PureComponent 避免过多 render function;
  • render里面尽量减少新建变量和bind函数,传递参数是尽量减少传递参数的数量;
  • 使用create-react-app来构建项目,这会创建整个项目结构,并进行大量优化。

性能检测工具

  • React.addons.Perf
  • Chrome Performance
  • React DevTools

React生命周期

初始化阶段:

constructor 构造函数

getDefaultProps props默认值

getInitialState state默认值

挂载阶段:

componentWillMount 组件初始化渲染前调用

render 组件渲染

componentDidMount组件挂载到 DOM后调用

更新阶段:

componentWillReceiveProps 组件将要接收新 props前调用

shouldComponentUpdate 组件是否需要更新

componentWillUpdate 组件更新前调用

render 组件渲染

componentDidUpdate 组件更新后调用

卸载阶段

componentWillUnmount 组件卸载前调用

16生命周期:

初始化阶段:

constructor 构造函数

getDefaultProps props默认值

getInitialState state默认值

挂载阶段:

static getDerivedStateFromProps(props,state)

render

componentDidMount

getDerivedStateFromProps:组件每次被 rerender的时候,包括在组件构建之后(虚拟 dom之后,实际 dom挂载之前),每次获取新的 props或 state之后;每次接收新的props之后都会返回一个对象作为新的 state,返回null则说明不需要更新 state;配合 componentDidUpdate,可以覆盖 componentWillReceiveProps的所有用法

更新阶段:

static getDerivedStateFromProps(props,state)

shouldComponentUpdate

render

getSnapshotBeforeUpdate(prevProps,prevState)

componentDidUpdate

getSnapshotBeforeUpdate:触发时间: update发生的时候,在 render之后,在组件 dom渲染之前;返回一个值,作为 componentDidUpdate的第三个参数;配合 componentDidUpdate, 可以覆盖 componentWillUpdate的所有用法

卸载阶段:

componentWillUnmount

错误处理:

componentDidCatch

React16新的生命周期弃用了componentWillMount、componentWillReceivePorps,componentWillUpdate新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate来代替弃用的三个钩子函数。

Fragments

使用 Fragments,不需要在DOM中添加额外的节点。我们只需要用 React.Fragment 或才简写 <> 来包裹内容就行了

错误边界

错误边界是一种 React 组件,可以捕获并打印其子组件树的JavaScript错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。如果一个 class 组件中定义了 static getDerivedStateFromError()或componentDidCatch()这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。