React 16.6新API
一.概览
新增了几个方便的特性:
React.memo:函数式组件也有“shouldComponentUpdate”生命周期了
React.lazy:配合Suspense特性轻松优雅地完成代码拆分(Code-Splitting)
static contextType:class组件可以更容易地访问单一Context
static getDerivedStateFromError():***友好的“componentDidCatch”
其中最重要的是Suspense特性,在之前的React Async Rendering中提到过:
另外,将来会提供一个suspense(挂起)API,允许挂起视图渲染,等待异步操作完成,让loading场景更容易控制,具体见Sneak Peek: Beyond React 16演讲视频里的第2个Demo而现在(v16.6.0,发布于2018/10/23),就是大约8个月之后的“将来”
二.React.memo
const MyComponent = React.memo(function MyComponent(props) { /* only rerenders if props change */});
还有个可选的compare参数:
function MyComponent(props) { /* render using props */}function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */}export default React.memo(MyComponent, areEqual);
类似于PureComponent的高阶组件,包一层memo,就能让普通函数式组件拥有PureComponent的性能优势:
React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.
内部实现
实现上非常简单:
export default function memo<Props>( type: React$ElementType, compare?: (oldProps: Props, newProps: Props) => boolean,) { return { $$typeof: REACT_MEMO_TYPE, type, compare: compare === undefined ? null : compare, };}
无非就是外挂式shouldComponentUpdate生命周期,对比class组件:
// 普通class组件class MyClassComponent { // 没有默认的shouldComponentUpdate,可以手动实现 shouldComponentUpdate(oldProps: Props, newProps: Props): boolean { return true; }}// 继承自PureComponent的组件相当于class MyPureComponent { // 拥有默认shouldComponentUpdate,即shallowEqual shouldComponentUpdate(oldProps: Props, newProps: Props): boolean { return shallowEqual(oldProps, newProps); }}// 函数式组件function render() { // 函数式组件,不支持shouldComponentUpdate}// Memo组件相当于const MyMemoComponent = { type: function render() { // 函数式组件,不支持shouldComponentUpdate } // 拥有默认的(挂在外面的)shouldComponentUpdate,即shallowEqual compare: shallowEqual};
如此这般,就给函数式组件粘了个shouldComponentUpdate上去,接下来的事情猜也能猜到了:
// ref: react-16.6.3/react/packages/react-reconciler/src/ReactFiberBeginWork.jsfunction updateMemoComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, updateExpirationTime, renderExpirationTime: ExpirationTime,): null | Fiber { // Default to shallow comparison let compare = Component.compare; compare = compare !== null ? compare : shallowEqual; if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) { return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } }}
所以,从实现上来看,React.memo()这个API与memo关系倒不大,实际意义是:函数式组件也有“shouldComponentUpdate”生命周期了
注意,compare默认是shallowEqual,所以React.memo第二个参数compare实际含义是shouldNotComponentUpdate,而不是我们所熟知的相反的那个。API设计上确实有些迷惑,非要引入一个相反的东西:
Unlike the shouldComponentUpdate() method on class components, this is the inverse from shouldComponentUpdate.
P.S.RFC定稿过程中第二个参数确实备受争议(equal, arePropsEqual, arePropsDifferent, renderOnDifference, isEqual, shouldUpdate...等10000个以内),具体见React.memo()
手动实现个memo?
话说回来,这样一个高阶组件其实不难实现:
function memo(render, shouldNotComponentUpdate = shallowEqual) { let oldProps, rendered; return function(newProps) { if (!shouldNotComponentUpdate(oldProps, newProps)) { rendered = render(newProps); oldProps = newProps; } return rendered; }}
手动实现的这个盗版与官方版本功能上等价(甚至性能也不相上下),所以又一个锦上添花的东西
三.React.lazy: Code-Splitting with Suspense
相当漂亮的特性,篇幅限制(此处删掉了276行),暂不展开
四.static contextType
v16.3推出了新Context API:
const ThemeContext = React.createContext('light');class ThemeProvider extends React.Component { state = {theme: 'light'}; render() { return ( <ThemeContext.Provider value={this.state.theme}> {this.props.children} </ThemeContext.Provider> ); }}class ThemedButton extends React.Component { render() { return ( // 这一部分看起来很麻烦,读个context而已 <ThemeContext.Consumer> {theme => <Button theme={theme} />} </ThemeContext.Consumer> ); }}
为了让class组件访问Context数据方便一些,新增了static contextType特性:
class ThemedButton extends React.Component { static contextType = ThemeContext; render() { let theme = this.context; return ( // 喧嚣停止了 <Button theme={theme} /> ); }}
其中contextType(注意,之前那个旧的多个s,叫contextTypes)只支持React.createContext()返回类型,翻新了旧Context API的this.context(变成单一值了,之前是对象)
用法上不那么变态了,但只支持访问单一Context值。要访问一堆Context值的话,只能用上面看起来很麻烦的那种方式:
// A component may consume multiple contextsfunction Content() { return ( // 。。。。 <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> );}
五.static getDerivedStateFromError()
static getDerivedStateFromError(error)
又一个错误处理API:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; }}
用法与v16.0的componentDidCatch(error, info)非常相像:
class ErrorBoundary extends React.Component { componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); }}
二者都会在子树渲染出错后触发,但触发时机上存在微妙的差异:
static getDerivedStateFromError:在render阶段触发,不允许含有副作用(否则多次执行会出问题)
componentDidCatch:在commit阶段触发,因此允许含有副作用(如logErrorToMyService)
前者的触发时机足够早,所以能够多做一些补救措施,比如避免null ref引发连锁错误
另一个区别是Did系列生命周期(如componentDidCatch)不支持***,而getDerivedStateFromError从设计上就考虑到了***(目前v16.6.3还不支持,但说了会支持)
目前这两个API在功能上是有重叠的,都可以在子树出错之后通过改变state来做UI降级,但后续会细分各自的职责:
static getDerivedStateFromError:专做UI降级
componentDidCatch:专做错误上报
六.过时API
又两个API要被打入冷宫:
ReactDOM.findDOMNode():性能原因以及设计上的问题,建议换用ref forwarding
旧Context API:性能及实现方面的原因,建议换用新Context API
P.S.暂时还能用,但将来版本会去掉,可以借助StrictMode完成迁移
七.总结
函数式组件也迎来了“shouldComponentUpdate”,还有漂亮的Code-Splitting支持,以及缓解Context Consumer繁琐之痛的补丁API,和职责清晰的UI层兜底方案
13种React组件
v16.6新增了几类组件(REACT_MEMO_TYPE、REACT_LAZY_TYPE、REACT_SUSPENSE_TYPE),细数一下,竟然有这么多了:
REACT_ELEMENT_TYPE:普通React组件类型,如<MyComponent />
REACT_PORTAL_TYPE:Protals组件,ReactDOM.createPortal()
REACT_FRAGMENT_TYPE:Fragment虚拟组件,<></>或<React.Fragment></React.Fragment>或[,]
REACT_STRICT_MODE_TYPE:带过时API检查的严格模式组件,<React.StrictMode>
REACT_PROFILER_TYPE:用来开启组件范围性能分析,见Profiler RFC,目前还是实验性API,<React.unstable_Profiler>稳定之后会变成<React.Profiler>
REACT_PROVIDER_TYPE:Context数据的生产者Context.Provider,<React.createContext(defaultValue).Provider>
REACT_CONTEXT_TYPE:Context数据的消费者Context.Consumer,<React.createContext(defaultValue).Consumer>
REACT_ASYNC_MODE_TYPE:开启异步特性的异步模式组件,过时了,换用REACT_CONCURRENT_MODE_TYPE
REACT_CONCURRENT_MODE_TYPE:用来开启异步特性,暂时还没放出来,处于Demo阶段,<React.unstable_ConcurrentMode>稳定之后会变成<React.ConcurrentMode>
REACT_FORWARD_REF_TYPE:向下传递Ref的组件,React.forwardRef()
REACT_SUSPENSE_TYPE:组件范围延迟渲染,<Suspense fallback={<MyLoadingComponent>}>
REACT_MEMO_TYPE:类似于PureComponent的高阶组件,React.memo()
REACT_LAZY_TYPE:动态引入的组件,React.lazy()
曾几何时,v15-只有1种REACT_ELEMENT_TYPE……
更多相关文章
- SpringMVC源码分析:一个request请求的完整流程和各组件介绍
- 几款代码高亮组件的体验,说不定你以后会用到
- 帆软报表自定义函数-取json数据
- 函数和递归
- java的getClass()函数
- 函数的学习
- java多线程(3)Thread构造函数解析
- JavaScript 测试教程–part 3:测试 props,挂载函数和快照测试[每日
- JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互