React性能
- React的的开发模式默认包含了很多有用的警告信息,这使得开发模式下的React性能与实际应该达到的效果大相径庭。所以当你需要关注性能时,请打包为部署版本。
- 你可以使用Chrome Performance标签分析组件来详细的了解各个组件的挂载、更新、卸载情况。
- 你需要临时禁用所有的chrome扩展,尤其时React开发者工具来排除他们对性能的影响。
- 你可以在生命周期方法
shouldComponentUpdate中返回false来阻止组件的视图更新- 当这个组件的更新缓慢到让人注意从,可以寻找在不需要更新时阻止他的更新。例如当且仅当state中的某个值发生改变才返回true,其他改变可以不更新则返回false。
- 这个方法默认返回true,他发生在更新之前,true代表允许视图更新
不可变性陷阱
陷阱:props来自state,值改变却没有更新视图。
- 将父组件的state作为子组件的props,父组件改变了state值,子组件的vDom中保存的是对父组件state的引用,改变后新的值也是父组件的state,导致新旧比较实际上比较的仍然是同一组state。(你在更改时使用了浅拷贝,setState是无效的操作,当然也不会有通知,浅拷贝来的副本在更改时整个调用链都被悄悄更新了)
- 避免改变你用于props的state值。你可以使用concat来返回一个新的对象来替换地址来引发更新,或者使用扩展运算符。两种方法都要求仅在setState中重设值,而不是通过创建副本的方式改变。
- 当然你也可以使用深拷贝来做这次操作。例如使用Object.assign返回一个深拷贝对象(可枚举深拷贝)
Profiler——性能测试
Profiler用来测量渲染一个React应用的性能指标,包括多久渲染一次以及其渲染的代价,他可以帮助识别应用中的性能短板。
Profiling增加了额外的开支,他在生产环境中是被禁用的。想做到这一点需要``从 fb.me/react-profi…了解更多关于如何使用这个构建环境的信息。
- 在JXS中使用<Profiler>标签来包裹你需要检测的部分。
- 他需要两个prop
- id:为它取个名字吧~
- onRender:当前组件树中的组件提交更新时的回调函数
- 他为你的回调函数传入的参数
Protals——脱离React树的节点挂载
- 用于将子节点渲染到任意节点而不是父节点以内的方法,可以做到脱离逻辑的节点挂载。
- 以下代码的render并没有把子元素渲染进他的调用位置。只是生成了一个domNode,你可以把它挂载在任何有效的dom节点上。
render() {
return ReactDOM.createPortal(
this.props.children,
domNode );
}
Protals节点的事件处理
- 需要注意的是,尽管Protals节点的挂载与逻辑无关,但是他在React树中的位置仍然符合逻辑。所以这仅仅是视图上绑定到了其他地方,但是组件逻辑事件或者数据环境方面仍然正常。
- 这包含事件冒泡,一个从Protals节点触发的事件会冒泡到React树的父节点。
- 这里的子元素render使用了
ReactDOM.createPortal并且在componentDidMount和componentWillUnmount中自定义了节点挂载位置,且挂载位置并不属于调用他的父组件结点下。
- 这里的子元素render使用了
Diff更新设计逻辑
- 我们做一些约定,这些约定也是在实际中几乎所有场景都成立的
- 两个不同类型的元素会产生不同的树
- 开发者使用keyProp来帮助React确定哪些元素在不同渲染下是稳定的
Diffing算法
- 对比两棵树时React首先会比较两棵树的根节点tag
- 当根节点的tag发生了变化,元素
类型不同,React会直接拆卸原有的树并构件新的树。例如div变成了a,或者组件A变成了组件B。- 拆卸和新键树意味着对应dom节点的销毁和挂载,两个生命周期钩子也很会被执行。
- 对比
同类型的元素,若当前元素的tag比对成功,会比较节点的props属性,找到属性中的不同并改变。- 当组件更新,不改变实例来维持state,调用
componentWillReceiveProps()和componentWillUpdate()方法来更新props值,然后调用render,diff将在新旧结果中进行递归,继续搜索子节点。
- 当组件更新,不改变实例来维持state,调用
- 当根节点的tag发生了变化,元素
- 继续对比子节点
- 默认情况下,递归子节点会简单的顺序同步遍历新旧子树序列,对发现的差异生成一个mutation。
- 当子元素拥有key值,则进行key的对比来确定发生变动的元素。
React,Vue2,Vue3的diff逻辑有所不同,且内容较多,留坑开贴细看。
Render Props
将组件的render中的一部分变成一个由父组件传递的props,组件可以使用自己的state渲染传入的props.render部分。
用来提取有共同的state与行为,但渲染内容不同的组件,当然你的渲染函数可以不叫render。
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
//在DataProvider组件中render就可以使用props.render来渲染使用自己的状态渲染父组件指定的JSX。
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
性能陷阱
不建议将Render Props与React.PureComponent共用。 留坑等看完这个API再饭回来写
严格模式
使用React.StrictMode标签包裹你要检查的JSX。
他可以用来突出显示你的程序中的潜在问题,他们仅在开发模式下运行,不会影响部署构建。
- 识别不安全的生命周期
- 对过时的或废弃的API使用情况的警告
- 帮助检测意外的函数副作用
非受控组件
file类型的input
<input type="file">用来让用户选择若干文件上传到服务器,他的值只能由用户设置,而不能通过代码控制,所以他必须是一个非受控组件。
- 当from中,将onsubmit事件接管并使用event.preventDefault将默认事件阻止。
- 使用file-input绑定的ref获取其中的文件信息
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef(); }
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}` );
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="file" ref={this.fileInput} />
<button type="submit">Submit</button>
</form>
);
}
}
React.Component API
生命周期
- constructor:构造组件实例
- render:构造react元素或其它形式的组件内容
- componentDidMount:将react元素挂载到真实dom树后
- componentDidUpdate:更新react元素后(props或state改变)
- componentWillUnmount:将组件从DOM中移除前,可以注销监听,清楚timer或取消网络请求
- componentDidCatch:当渲染过程,生命周期,子函数构造函数中抛出错误时
API
render
- render:必须实现的方法,当render被调用时,检查props和state的变化并返回以下内容之一
- React元素:通过JSX创建
- fragments或数组:使得render返回多个React元素(Vue的template中只能有一个根节点同理)
- Portals:可以将其渲染到任意dom结点下的元素
- 文本dom结点
- null,什么都不渲染
- render应该是纯函数,且不与浏览器直接交互
- 如果
shouldComponentUpdate返回false,则不会调用render constructor - 如果你无需初始化state或不进行方法的绑定则不需要实现constructor
- 在React.Component子类实现构造函数时,应该首先调用super(props),否则会出现this.props未定义的bug。
- 避免将props复制给state,除非你要刻意的忽略父元素发来的props更新
- 关于React官网中一篇关于避免从paops派生state的文档:请保证任何数据的来源都是单一的,且整个组件或项目的数据流动是单向的。做到这一点可以避免很多问题。 componentDidMount
- 当真实Dom挂在后立即调用,依赖于dom结点的初始化应该实现在此。例如需要请求网络数据此处是实例化请求的好地方。
- 这里是添加全局订阅的好地方,但是不要忘记在component中取消订阅。
- 你可以在此调用setstate,他会触发额外的渲染,但是本次渲染会发生在GUI更新之前,这两次render的中间态并不会被用户看到。
- 请谨慎使用该模式,它会导致性能问题,除非你的渲染依赖于真实dom数据。 componentDidUpdate
- 参数为
prevProps,prevState,snapshot- 更新前的老props和state
- 如果实现了
getSnapshotBeforeUpdate生命周期钩子,则第三个函数是这个钩子的返回值
- 除了首次渲染,每次更新渲染之后会立即调用
- 比如你可以在此对比props,如果你关注的某个数据发生变化则请求新的对应网络数据,如更换了用户。
- 你可以在此使用setstate,但必须包裹在条件语句中,否则会产生环路。
- 如果
shouldComponentUpdate返回了false,则不会调用此函数
一些不常用的生命周期方法
shouldComponentUpdate
- 当props或state更新后,render触发前调用
- 根据它的返回值来告诉React是否要重新渲染render,默认情况下总会返回true
- 此方法仅仅用于性能优化的一种方式,不用使用此方法来阻止渲染的更新。
- 如果你需要阻止React的渲染,考虑使用
PureComponent组件。这种组件于普通组件的区别为对props进行浅层比较来跳过必要更新的可能性。 - 使用PureComponent后,可以使用forceUpdate(强制重新渲染)来确保某些你关注的深层变化时可以被更新渲染。且这种组件会忽视所有子组件树的props更新。
- 如果你需要阻止React的渲染,考虑使用
- 不建议在此进行深层比较或者使用Json.stringify,他们会严重的影响效率
- 后续版本的React再考虑将此函数的返回值仅作参考而不是强制指令,也就是即使你返回了false也可能会发生重新渲染。 static getDerivedStateFromProps
- render之前被调用,适用于非常罕见的state值任何时候都取决于props。 getSnapshotBeforeUpdate
- dom更新之前调用,可以在更新dom之前捕获一些dom信息 static getDerivedStateFromError
- 在后代组件抛出错误时被调用,将抛出的错误作为参数并返回一个更新state的值,一般用于降级UI。
- 他在渲染时被调用,不允许出现副作用 componentDidCatch
- 在后代组件抛出错误后调用,接收两个参数
- error:抛出的错误
- ifo:带有componentStack key的对象,包含有关组件引发错误栈的信息。
- 他在提交阶段被调用,允许副作用。一般用于记录错误
事件
关于React事件参数实例或者封装好的一些常用事件: 合成事件 – React (docschina.org)