关于 React Hooks 的二三事

先从 renderProps 说起吧

在 class component 各种生命周期盛行的时候,没有 state 的函数通常来做展示型组件,所以社区就有人想出增强 function component 功能的方法。

react-powerplug 是一个 renderProps 的工具库,来看下它的代码

1
2
3
4
5
6
7
8
9
10
11
// father component
<Toggle initial={false}>
{({ on, toggle }) => (
<CheckBox onChange={toggle} checked={on} />
)}
</Toggle>

// child component
function CheckBox(props) {
return <input type="checkbox" {...props} />
}

对比下 class component 的写法,是少了很多的 state 和一些回调函数了

1
2
3
4
5
6
7
8
9
10
state = { checked: false }

handleChange = () => { this.setState({ checked: !this.state.checked }) };

render() {
const { checked } = this.state;
return (
<CheckBox onChange={this.handleChange} checked={checked} />
)
}

不过大家应该可以看出一点局限性了,比如 Toggle 只适合做显隐控制这些,业务一些状态可能并不适用。

还有存在嵌套的场景,最新版本去掉了 compose 聚合方法,那只能另寻出路了。

1
2
3
4
5
6
7
8
9
10
11
12
13
function App() {
return (
<Counter initial={5}>
{counter => (
<Toggle initial={false}>
{({ on, toggle }) => (
<CheckBox counter={counter} onChange={toggle} checked={on} />
)}
</Toggle>
)}
</Counter>
);
}

解决嵌套问题的epitath

1
2
3
4
5
const App = epitath(function*() {
const { counter } = yield <Counter />;
const { on, toggle } = yield <Toggle />;
return <CheckBox counter={counter} onChange={toggle} checked={on} />;
});

epitath 其源码一共40行,看看是如何实现的。

核心就在于 immutable 的 generator + React.cloneElement

1
2
3
const compose = ({ next, value }) => next
? React.cloneElement(value, null, values => compose(next(values)))
: value
1
2
3
4
5
6
7
8
9
10
yield A
yield B
yield C

// =>
<A>
<B>
<C />
<B />
<A />

Hooks

Hooks 的出现,替代了 mixin, HOC 复用代码的方式。

在上节中我们说到 Hooks 就和普通 function component 一样,是有 capture value 特性的。

反映到下面的 demo 上,实际效果并不是从 60 减到 0,而是到达 59 暂停,解决办法在之前也提到过,可以用 useRef 拿到最新的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Timeout() {
const [counter, setCounter] = useState(60);
const [start, setStart] = useState(false);
useEffect(() => {
let intervel;
if (start) {
intervel = setInterval(() => {
setCounter(counter - 1)
}, 1000);
}
return () => clearInterval(intervel)
}, [start])
return <>
<p>{counter}</p>
<button onClick={() => setStart(!start)}>CLICK ME!</button>
</>
}

Hooks 的高复用性,让它用来制作自定义 Hooks 十分方便。