You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
···componentDidCatch(error,info){// Display fallback UIthis.setState({hasError: true});// You can also log the error to an error reporting servicelogErrorToMyService(error,info);}···
通过框架级别的 API 来约束甚至限制开发者写出更易维护的 Javascript 代码,最大限度的避免了反模式的开发方式。
全新的 Context API
在 React 16.3 之前,Context API 一直被官方置为不推荐使用(don’t use context),究其原因是因为老的 Context API 作为一个实验性的产品,破坏了 React 的分形结构。同时在使用的过程中,如果在穿透组件的过程中,某个组件的 shouldComponentUpdate 返回了 false, 则 Context API 就不能穿透了。其带来的不确定性也就导致被不推荐使用。随着 React 16.3 的发布,全新 Context API 成了一等 API,可以很容易穿透组件而无副作用,官方示例代码:
// Context lets us pass a value deep into the component tree// without explicitly threading it through every component.// Create a context for the current theme (with "light" as the default).constThemeContext=React.createContext('light')classAppextendsReact.Component{render(){// Use a Provider to pass the current theme to the tree below.// Any component can read it, no matter how deep it is.// In this example, we're passing "dark" as the current value.return(<ThemeContext.Providervalue="dark"><Toolbar/></ThemeContext.Provider>)}}// A component in the middle doesn't have to// pass the theme down explicitly anymore.functionToolbar(props){return(<div><ThemedButton/></div>)}functionThemedButton(props){// Use a Consumer to read the current theme context.// React will find the closest theme Provider above and use its value.// In this example, the current theme is "dark".return(<ThemeContext.Consumer>{theme=><Button{...props}theme={theme}/>}</ThemeContext.Consumer>)}
// before React 16···componentDidMount(){// the refs object container the myRefconstel=this.refs.myRef// you can also using ReactDOM.findDOMNode// const el = ReactDOM.findDOMNode(this.refs.myRef)}render(){return<divref="myRef"/>}······// React 16+constructor(props){super(props)this.myRef=React.createRef()}render(){return<divref={this.myRef}/>}···
constTextInput=React.forwardRef((props,ref)=>(<inputtype="text"placeholder="Hello forwardRef"ref={ref}/>))constinputRef=React.createRef()classAppextendsComponent{constructor(props){super(props)this.myRef=React.createRef()}handleSubmit=event=>{event.preventDefault()alert('input value is:'+inputRef.current.value)}render(){return(<formonSubmit={this.handleSubmit}><TextInputref={inputRef}/><buttontype="submit">Submit</button></form>)}}
目录
0. 生命周期函数的更新
1. 全新的 Context API
2. React Strict Mode
3. Portal
4. Refs
5. Fragment
6. 其他
7. 总结
生命周期函数的更新
随着 React 16.0 发布, React 采用了新的内核架构 Fiber,在新的架构中它将更新分为两个阶段:Render Parse 和 Commit Parse, 也由此引入了
getDerivedStateFromProps
、getSnapshotBeforeUpdate
及componentDidCatch
等三个生命周期函数。同时,也将componentWillUpdate
、componentWillReceiveProps
和componentWillUpdate
标记为不安全的方法。新增
static getDerivedStateFromProps(nextProps, prevState)
getSnapshotBeforeUpdate(prevProps, prevState)
componentDidCatch(error, info)
标记为不安全
componentWillMount(nextProps, nextState)
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
static getDerivedStateFromProps(nextProps, prevState)
根据
getDerivedStateFromProps(nextProps, prevState)
的函数签名可知: 其作用是根据传递的props
来更新state
。它的一大特点是 无副作用 : 由于处在 Render Phase 阶段,所以在每次的更新都要触发, 故在设计 API 时采用了静态方法,其好处是单纯 —— 无法访问实例、无法通过ref
访问到 DOM 对象等,保证了单纯且高效。值得注意的是,其仍可以通过props
的操作来产生副作用,这时应该将操作props
的方法移到componentDidUpdate
中,减少触发次数。例:
但在使用时要非常小心,因为它不像
componentWillReceiveProps
一样,只在父组件重新渲染时才触发,本身调用setState
也会触发。官方提供了 3 条 checklist, 这里搬运一下:props
的同时,有副作用的产生(如异步请求数据,动画效果),这时应该使用componentDidUpdate
props
计算属性,应该考虑将结果 memoization 化,参见 memoizationprops
变化来重置某些状态,应该考虑使用受控组件配合
componentDidUpdate
周期函数,getDerivedStateFromProps
是为了替代componentWillReceiveProps
而出现的。它将原本componentWillReceiveProps
功能进行划分 —— 更新state
和 操作/调用props
,很大程度避免了职责不清而导致过多的渲染, 从而影响应该性能。getSnapshotBeforeUpdate(prevProps, prevState)
根据
getSnapshotBeforeUpdate(prevProps, prevState)
的函数签名可知,其在组件更新之前获取一个 snapshot —— 可以将计算得的值或从 DOM 得到的信息传递到componentDidUpdate(prevProps, prevState, snapshot)
周期函数的第三个参数,常常用于 scroll 位置的定位。摘自官方的示例:componentDidCatch(error, info)
在 16.0 以前,错误捕获使用
unstable_handleError
或者采用第三方库如 react-error-overlay 来捕获,前者捕获的信息十分有限,后者为非官方支持。而在 16.0 中,增加了componentDidCatch
周期函数来让开发者可以自主处理错误信息,诸如展示,上报错误等,用户可以创建自己的Error Boundary
来捕获错误。例:此外,用户还可以采用第三方错误追踪服务,如 Sentry、Bugsnag 等,保证了错误处理效率的同时也极大降级了中小型项目错误追踪的成本。
标记为不安全
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
componentWillMount
componentWillMount
可被开发者用于获取首屏数据或事务订阅。开发者为了快速得到数据,将首屏请求放在
componentWillMount
中。实际上在执行componentWillMount
时第一次渲染已开始。把首屏请求放在componentWillMount
的与否都不能解决首屏渲染无异步数据的问题。而官方的建议是将首屏放在constructor
或componentDidMount
中。此外事件订阅也被常在
componentWillMount
用到,并在componentWillUnmount
中取消掉相应的事件订阅。但事实上 React 并不能够保证在componentWillMount
被调用后,同一组件的componentWillUnmount
也一定会被调用。另一方面,在未来 React 开启异步渲染模式后,在 · 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致componentWillUnmount
不会被调用。而componentDidMount
就不存在这个问题,在componentDidMount
被调用后,componentWillUnmount
一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。对此的升级方案是把
componentWillMount
改为componentDidMount
即可。componentWillReceiveProps
、componentWillUpdate
componentWillReceiveProps
被标记为不安全的原因见前文所述,其主要原因是操作 props 引起的 re-render。与之类似的componentWillUpdate
被标记为不安全也是同样的原因。除此之外,对 DOM 的更新操作也可能导致重新渲染。对于
componentWillReceiveProps
的升级方案是使用getDerivedStateFromProps
和componentDidUpdate
来代替。对于
componentWillUpdate
的升级方案是使用componentDidUpdate
代替。如涉及大量的计算,可在getSnapshotBeforeUpdate
完成计算,再在componentDidUpdate
一次完成更新。通过框架级别的 API 来约束甚至限制开发者写出更易维护的 Javascript 代码,最大限度的避免了反模式的开发方式。
全新的 Context API
在 React 16.3 之前,Context API 一直被官方置为不推荐使用(don’t use context),究其原因是因为老的 Context API 作为一个实验性的产品,破坏了 React 的分形结构。同时在使用的过程中,如果在穿透组件的过程中,某个组件的
shouldComponentUpdate
返回了false
, 则 Context API 就不能穿透了。其带来的不确定性也就导致被不推荐使用。随着 React 16.3 的发布,全新 Context API 成了一等 API,可以很容易穿透组件而无副作用,官方示例代码:其过程大概如下:
React.createContext
创建 Context 对象<ThemeContext.Provider/>
来提供 Provider<ThemeContext.Consumer/>
以函数调用的方式{theme => <Button {...props} theme={theme} />}
获得 Context 对象的值。Context API 与 Redux
在状态的管理上,全新的 Context API 完全可以取代部分 Redux 应用,示例代码:
全新的 Context API 带来的穿透组件的能力对于需要全局状态共享的场景十分有用,无需进入额外的依赖就能对状态进行管理,代码简洁明了。
React Strict Mode
React StrictMode 可以在开发阶段发现应用存在的潜在问题,提醒开发者解决相关问题,提供应用的健壮性。其主要能检测到 4 个问题:
使用起来也很简单,只要在需要被检测的组件上包裹一层
React StrictMode
,示例代码 React-StictMode:若出现错误,则在控制台输出具体错误信息:
Portal
由 ReactDOM 提供的
createPortal
方法,允许将组件渲染到其他 DOM 节点上。这对大型应用或者独立于应用本身的渲染很有帮助。其函数签名为ReactDOM.createPortal(child, container)
,child
参数为任意的可渲染的 React Component,如element
、sting
、fragment
等,container
则为要挂载的 DOM 节点.以一个简单的 Modal 为例, 代码见 Portal Modal :
具体过程就是使用了
props
传递children
后, 使用ReactDOM.createPortal
, 将 container 渲染在其他 DOM 节点上的过程。Refs
虽然 React 使用 Virtual DOM 来更新视图,但某些时刻我们还要操作真正的 DOM ,这时
ref
属性就派上用场了。React.createRef
React 16 使用了
React.createRef
取得 Ref 对象,这和之前的方式还是有不小的差别,例:React.forwardRef
另外一个新特性是 Ref 的转发, 它的目的是让父组件可以访问到子组件的 Ref,从而操作子组件的 DOM。
React.forwardRef
接收一个函数,函数参数有props
和ref
。看一个简单的例子,代码见 Refs:这个例子使用了
React.forwardRef
将props
和ref
传递给子组件,直接就可以在父组件直接调用。Fragment
在向 DOM 树批量添加元素时,一个好的实践是创建一个
document.createDocumentFragment
,先将元素批量添加到DocumentFragment
上,再把DocumentFragment
添加到 DOM 树,减少了 DOM 操作次数的同时也不会创建一个新元素。和
DocumentFragment
类似,React 也存在Fragment
的概念,用途很类似。在 React 16 之前,Fragment 的创建是通过扩展包react-addons-create-fragment
创建,而 React 16 中则通过<React.Fragment></React.Fragment>
直接创建 'Fragment'。例如:如此,我们不需要单独包裹一层无用的元素(如使用
<div></div>
包裹),减少层级嵌套。此外,还一种精简的写法:
其他
ReactDOM
的render
函数可以数组形式返回 React Component移除内建的
react-with-addons.js
, 所有的插件都独立出来之前常用的
react-addons-(css-)transition-group
,react-addons-create-fragment
,react-addons-pure-render-mixin
、react-addons-perf
等,除部分被内置,其余全部都独立为一个项目,使用时要注意。总结
窥一斑而见全豹,React 16.0 ~ 16.5 的升级给了开发者一个更为纯粹的开发流程。API 层面的更改、架构的更替、工具类的拆分都在为构建更易维护的 JavaScript 应用而努力。拥抱变化,顺应时势。
由于笔者能力有限,文中难免有疏漏,还望读者不吝赐教。
以上。
参考:
React Docs
Update on Async Rendering
You Probably Don't Need Derived State
React v16.3 版本新生命周期函数浅析及升级方案
React 16: A look inside an API-compatible rewrite of our frontend UI library
React Fiber Architecture
The text was updated successfully, but these errors were encountered: