一.概览
新增了几个方便的特性:
-
React.memo
:函数式组件也有“shouldComponentUpdate”生命周期了 -
React.lazy
:配合Suspense特性轻松优雅地完成代码拆分(Code-Splitting) -
static contextType
:class组件可以更容易地访问单一Context -
static getDerivedStateFromError()
:SSR友好的“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.js function 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 contexts function 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
)不支持SSR,而 getDerivedStateFromError
从设计上就考虑到了SSR(目前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
……