一.概览
新增了几个方便的特性:
React.memo
:函数式组件也有“shouldComponentUpdate”生命周期了React.lazy
:配合Suspense特性轻松优雅地完成代码拆分(Code-Splitting)static contextType
:class组件可以更容易地访问单一Contextstatic 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
相当漂亮的特性,篇幅限制,具体见React Suspense
四.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
……