你也想做掌控全局的 React 大师吗?
你可能已经听说过大名鼎鼎的 why-did-you-update ,它能够发现程序中不必要的重新渲染:
其大致原理是将 React.Component.prototype.componentDidUpdate 覆盖为一个新的函数,在其中进行了每次渲染前后的 props 的深度比较,并将结果以友好直观的方式呈现给用户。但它有一个明显的缺陷——如果某一组件定义了 componentDidUpdate 方法, why-did-you-update 就失效了。
想要解决上述问题、并且更进一步地了解其他生命周期的触发情况,或者说,以上帝视角看到整个 React 应用的运行情况,该怎么做呢?
最直接的做法是,对每个组件修改其代码或使用 HoC 来达到目的。但有此类需求的项目体积往往已经具备一定规模,逐个修改代码的做法是不可接受的,想象一下在每个组件的代码都进行这样的修改会是多么繁琐的工作:
class App extends React.Component {
// 手动添加该方法
componentDidUpdate() {
// ...
}
render() {
return 'Hello, World!'
}
}
// 或者使用 decorator
@addDidUpdate
class App extends React.Component {
render() {
return 'Hello, World!'
}
}
那么有没有一款轻松易用,可以在一处监听项目中所有组件的工具呢?
有!
EnixCoda/ReactLifecycleHooksgithub.com
react-lifecycle-hooks 可以帮助你可以对项目中所有组件的 React 生命周期事件进行监听,而完全不需要修改原有代码。
// 假设这是项目中已有的一个组件, app.js
class App extends React.Component {
render() {
return 'Hello, World!'
}
}
// 新建的监听专用文件,在必要时引入你的项目
import { activate, addMiddleware } from 'react-lifecycle-hooks'
import App from './path/to/app'
activate() // 这一行就都安排上了!
// 示例中间件,中间件在所有组件的所有生命周期触发时被调用,你可以通过如下过滤实现定向监听
function simplyLog(componentClass, componentInstance, lifecycleName, lifecycleArguments) {
if (componentClass === App) {
console.log('Going to execute', lifecycleName, 'of', componentClass.name, 'on', componentInstance)
}
}
addMiddleware(simplyLog) // 从此开始,所有 App 组件上发生的事情会被 simplyLog 呈现于 console
原理揭秘
是 JSX ,我加了 JSX 。
一切的秘密其实在 JSX ,让我们重新审视 JSX :
<Component prop={value}>{children}</Component>
JSX 是一种语法,在 React 下其对应的原生 JavaScript 表示约为
React.createElement(Component, { prop: value }, children)
React 的每一次渲染实际上是多次调用了 React.createElement 方法,生成对应的 JavaScript Object (VDOM)来表达视图。如果改写该方法,将记录每个传入的 Component ,就能追踪到所有被渲染的组件。
核心代码摘要:
const { createElement } = react
function decorate(componentClass) { /* HoC */ }
react.createElement = function (type) {
if (typeof type !== 'function') return createElement.apply(this, arguments)
// type 是 组件类 或 无状态组件
const decorated = decorate(type)
return createElement.apply(this, [decorated].concat(Array.prototype.slice.call(arguments, 1)))
}
于是每次渲染出的 VDOM 中都是被 decorate 方法处理过的新组件,带有全套的生命周期方法。而且,由于新组件的生命周期是对原组件的巧妙包装,使用 react-lifecycle-hooks 不会对你的应用表现产生任何的影响,达到了无缝切入的效果。强烈建议你玩一下上方的在线 demo !
展望
react-lifecycle-hooks 的不是开箱即用可以直接为你提高生产力、解决实际问题的库,它只是提供了一个便于你掌握应用中 React 组件动态的入口,因此更像是为打造其他实用库而出现的基础库,比如在它之上可以开发出类似 React DevTools 的实用工具或优化 why-did-you-update!
// 简单改写 why-did-you-update
function whyDidYouUpdate(componentClass, componentInstance, lifecycleName, lifecycleArguments) {
if (lifecycleName === 'componentDidUpdate') {
const [ props, state ] = lifecycleArguments
const prev = { props, state }
const cur = { props: componentInstance.props, state: componentInstance.state }
deepCompareAndPrint(prev, cur) // why-did-you-update 的深比较、输出方法
}
}