合成事件
动机: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() 打印错误信息。