From React Hooks to Component Class
React 支持通过 class
或 function
两种方式定义 Component 。但 React Team 后来推荐大家使用 Hooks 通过 function 来定义组件。认为这是一种新的思维模式(mental Model)。
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
从 class 到 function with hooks 当然有其背后的 Motivation 。
一个有意思的比较方式是,反过来想,假如是从 Function Hooks 转换到 class 会是什么样子。
useState()
简单的计数组件,点击按钮,计数加一。
function Counter(){
let [count, setCount] = useState(0)
let increment = function(){
setCount(count+1);
}
return <button click={increment}>{count} +1</button>
}
class Counter extends React.Component {
constructor(props){
super(props);
this.state = {count:0}
this.increment = this.increment.bind(this);
}
increment(){
this.setState({count: this.state.count);
}
render(){
return <button click={this.increment}>{this.state.count}+1</button>
}
}
上面的改写对 class 来说有一点时不公平的。
- function 形式直接访问
count
值 - class 却需要通过
this.state
间接访问count
值 - function 形式通过
setCount()
修改状态 - class 通过统一的
this.setState()
修改状态
State in React Class
React 文档关于 class 的 state 提出了三条注意事项:
- Do Not Modify State Directly
- State Updates May Be Asynchronous
- State Updates are Merged
其中第一条指的就是所有状态都需要通过 this.setState()
来修改。
这条规则背后的意义在于:React 需要监听状态数据的变化。
而监听的实现实际上可以让用户显示地来完成。
从 this.state.count 到 this.count
我们先假设我们可以直接使用单个状态数据。并且显示地通知 React 状态发生了改变。
class Counter extends React.Component {
constructor(props){
super(props);
this.count = 0;
}
increment(){
this.count = this.count + 1;
this.notifyUpdate({}}); // 通知 React 状态发生了改变
}
render(){
return <button click={this.increment}>{this.count} +1</button>
}
到这里为止,和前面 function 形式的组件相比,差别主要是
- 多了一个
constructor()
- 多了一个
this.notifyUpdate()
- 少了一个
setCount()
- 少了一个
useState()
在 class 里使用 this.useState()
如果添加上少掉的 useState()
, class 形式的 Counter 定义如下。
class Counter extends React.Component {
constructor(props){
super(props);
[this.count, this.setCount] = this.useState('count', 0);
this.increment = this.increment.bind(this);
}
increment(){
this.setCount(this.count + 1);
}
render(){
return <button onClick={this.increment}>{this.count} + 1</button>
}
这样一来,可以说差别就只是多了 constructor()
和 this
这个指针。
实现 this.useState()
理想情况下,当然时定义在 React.Component 的实现里。
class React.Component {
useState(name, initValue){
let $this = this, $state = this.state;
$state[name] = initValue;
let setValue = function(newValue){
$state[name] = newValue;
$this.setState({});
}
return [$state[name], setValue.bind($this)]
}
}
render(props, state)
函数形式的 React Component 里, props
作为参数每次都参与了“计算”。
class 形式里面则只在 constructor()
时传入了 props
, 其他时候隐式地通过类似于 setProps()
的方式改变。
用函数的形式描述,组件的创建和更新类似于 view = render(props, state)
的多次调用。
因此采用 render(props, state)
的函数形式可能更准确,也更方便。
当然,并不是每个 Component 都需要维护一个 state
,这种情况可以等价于 render(props, null)
来处理。
class Counter extends React.Component {
...
render(props, state){
return <button onClick={this.increment}>{state.count} + 1</button>
}
}
自动绑定 .bind(this)
既然 React 已经选择了通过 JXS 自动生成代码的方式,而且模板里已经清楚地写明了需要调用 this.handleEvent()
,那么帮助用户自动绑定 this
指针可以说是“举手之劳”的事情。
注册 Hook 函数的 useEffect()
React Hooks 里面的 useEffect() 的关键点有两处
- 合并了
componentDidMount()
和componentDidUpdate()
- 返回一个函数用于在
componentWillUnmount()
clean up
这两点功能如果是在 class 里,可以用 this.registerHook()
来实现。
之所以叫做 registerHook()
,是因为 useEffect()
定义的函数正是传统意义上的 Hook —— 在特定节点上调用预先注册的代码。
引用外部数据的 useContext()
useContext()
实质上是对外部数据的引用。这个外部数据类似于多个 Component 所共享的“全局”数据。
同时 useContext()
可以告诉框架当前 Component 需要对这个“全局”数据进行 react
useRef() == this.state
React Hooks 模式相当于默认使用 State 的 snapshot 值。需要使用 State 的最新值得话需要借助 useRef()
函数。
而在 class component 里,默认情况下 this.state
就是最新的状态值。
是否需要区分最新值和映像值,要视具体情况而定。很多时候应该是不需要做严格区分的。
在暂且假定使用映像值有利于后期代码优化的假设下,可以给回调函数显示地传入 state
的方式来处理。
这可以通过使用 callback.bind(this, props, state)
实现。
实现 memoized 的 useCallback() 和 useMemo()
useCallback() 和 useMemo() 是辅助函数。和是 class 形式还是 function 形式关系不大。
可能的 React Class 写法
根据上述对比,我们也许可以用下面的形式来定义 React Component
class Counter extends React.Component {
constructor(props){
super(props);
this.defineState('count', 0); // 定义状态 == useState()
this.useContext(MyContext); // 外部状态 == useContext()
this.registerHook(this.changeTitle); // 生命周期 == useEffect()
this.registerHook(model.fetchStatus); // 重用外部逻辑
}
render(props, state){
return <button onClick={this.increment}>{state.count} + 1</button>
}
increment(props, state){ // 有助于暗示这是状态操作,同时传入了旧的状态映像
this.updateState('count', state.count + 1); // 更新状态
}
changeTitle(){
document.title = "hello";
let cleanup = function(){
// ...
}
return cleanup
}
}
React class 还是 React function ?
总结上面的对比,Function 的写法主要优点是避免了 this
的使用。
而 class 形式的优点在于通过代码拆分,结构实际上更清楚。
对于状态数据的管理,function 形式实际上是把状态数据的管理完全交由框架统一管理。 而 class 则是自己管理,严格来说,这更符合封装的含义。
对并发的处理
React 转向 Hooks 的动机之一是并行的状态更新。
对此,TODO.