由React Hooks所想到的
继Fiber,Suspence之后,最近React又提出了一版突破原有理论的API——Hooks,真是难以想象这都是一个版本之内做出的改变。React真的是太强了,学不动了。。
期望解决的问题
当然调侃归调侃,还是要紧跟时代的步伐。彻底的变化必然是能够解决相当大的痛点的。那么Hooks的目的是什么呢?本质上就是五个字——复用和维护。先不谈这个API的具体细节,我们先想想现在的React在复用和维护上有什么问题。
当使用无论什么框架时,我们大概都会经历这样一个过程——先是一个简单的组件,随着业务升级,组件越来越复杂,那么首先想到的就是拆分为可复用的小组件。当完成拆分后就会有数据通信的问题,于是有了各种各样的状态管理工具来解决这个问题,将组件间通信大致分为两种模式——通过redux之类的全局状态通信和通过props向下传递状态的通信,同时在拆分过程中,为了更好的复用一些逻辑,又创造了HOC和render props的组件组织形式。
那么最大的问题就来了——为了解决JS层面复用的问题,我们不得不在UI层面创建大量的无关UI的wrapper组件去单独处理JS复用,这样不仅带来了props通信困难的问题,同时使得组件树变得越来越臃肿,调试和维护的成本也越来越高。函数式编程提倡的data到view的映射中间,不得不生硬的增加一层view去处理data,而这一层本来是更适合用JS去做的事,为何不直接就在data到view这一层通过JS处理掉呢?
Hooks所做的就是这样一件事,函数式组件不再是单纯的data到view的映射关系,而在其中追加上了一层“映射所依赖的逻辑和状态”,类似于函数式编程中的Monad,这样一下子就解决了状态管理和逻辑复用的难点。
- 没有依赖的状态放置到各自的组件Hooks中单独管理,和全局状态管理解耦的同时增加组件的内聚,同时这层Hooks可以和view的渲染拆开,更方便复用。
- 组件的逻辑复用层面也将从HOC或者render props中抽离到各自组件的Hooks,打平组件的层次,将这些JS层面的东西从组件树中剥离,成为一个真正的独立可复用的逻辑单元。
那么下面就该看看Hooks究竟是如何使用的了。
使用
首先要知道它的本意就是在纯函数中hook一些依赖。必须遵守如下两条规则。
- 只能在函数式组件中调用Hooks
- 只能在函数顶层调用,不能出现在循环或者条件判断中。
大致可以分为如下几类。
State Hook
用于在函数式组件中使用state。这个Hook将原先每个组件的唯一state拆开,并将state中的每个值和能改变其值并重渲染的方法关联。官方用例如下:
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
可以看到这样的写法相当直观清爽,一眼就能看到state相关联的变量和方法,同时这部分声明state的逻辑也更容易从这个函数式组件中拆出以实现复用。
Effect Hook
从class组件迁移到function组件需要解决的另一个问题就是,class组件从创建到销毁所经过的生命周期。生命周期的引入无非就是为了在某个特定阶段做一些sideEffects,那么为什么不把这个阶段统一成一个API呢?实际上React本身就这么做了,从getDerivedStateFromProps
的提出到Effect Hook,React有意的弱化生命周期的概念,将重点从"组件生命周期本身"迁移到"数据的来源与获取"上来,这样当然是更纯粹的,React的核心本身也就是数据和渲染,所有适合在生命周期做的事转移到数据到渲染过程中的sideEffects。官方实例如下:
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
其他
其实以上两类Hooks已经能大致了解这个新API了,除此之外还有更多的Hooks也可以查阅官方文档,这里就不赘述。使用上还是很简单的,关键还是要理解其在"复用和维护"上传达出来的新的思想,可以看一则官方拆分实例感受一下。
import React from 'react';
import { Card, Row, Input, Text } from './components';
import ThemeContext from './ThemeContext';
export default class Greetings extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Mary',
surname: 'Popins',
width: width.innerWidth
};
this.handleNameChange = this.handleNameChange.bind(this);
this.handleSurnameChange = this.handleSurnameChange.bind(this);
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
document.title = this.state.name + '' + this.state.surname;
}
componentDidUpdate() {
document.title = this.state.name + '' + this.state.surname
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
handleNameChange(event) {
this.setState({name: event.target.value});
}
handleSurnameChange(event) {
this.setState({surname: event.target.value});
}
handleResize() {
this.setState({width: innerWidth});
}
render() {
const { name, surname, width } = this.state;
return (
<ThemeContext.Consumer>
{theme => (
<Card theme={theme}>
<Row label="Name">
<Input value={name} onChange={this.handleNameChange}/>
</Row>
<Row label="Surname">
<Input value={surname} onChange={this.handleSurnameChange}/>
</Row>
<Row label="Width">
<Text>{width}</Text>
</Row>
</Card>
)}
</ThemeContext.Consumer>
)
}
}
import React, { useState, useContext, useEffect } from 'react';
import { Card, Row, Input, Text } from './components';
import ThemeContext from './ThemeContext';
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
}
}
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
});
}
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handlewResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
});
return width;
}
export default function Greetings(props) {
const name = useFormInput('Mary');
const surname = useFormInput('Poppins');
const theme = useContext(ThemeContext);
const width = useWindowWidth();
useDocumentTitle(name.value + '' + surname.value);
return (
<Card theme={theme}>
<Row label="Name">
<Input {...name}/>
</Row>
<Row label="Surname">
<Input {...surname}/>
</Row>
<Row label="Width">
<Text>{width}</Text>
</Row>
</Card>
)
}
小结
可以看出现在React中函数式组件可以表达的东西越来越多,函数式编程真的是可以重点学习的一块了,毕竟data到view的映射本质就很适合用函数式的思想去写,再搭配上hooks引入的monad思想,rxjs等函数式编程思想的状态管理框架,组件不就可以用state和sideEffect完美表达了吗?随着Fiber的落地,未来的前端开发真的就是只有想不到没有做不到了。
-- EOF --