做者:元潇 方凳雅集出品javascript
16.8目前放出来了10个内置hook,但仅仅基于如下两个API,就能作不少事情。因此这篇文章不会讲不少API,也不会讲API的基本用法,只把这两个能作的事情讲清楚,阅读全文大概5-10分钟。java
这两个api就是hook世界里的镰刀和锤子,看似简单的两个api实际上所表明的,是相比之前大相径庭的一种新的编程模型。react
Dan在他的博客上提到:编程
咱们知道组件和自上而下的数据流能够帮助咱们将大型UI组织成小型,独立,可重用的部分。 可是,咱们常常没法进一步破坏复杂组件,由于逻辑是有状态的,没法提取到函数或其余组件中。而hook让咱们能够将组件内部的逻辑组织成可重用的隔离单元。
因此,一句话总结hook带来的变革就是:将可复用的最小单元从组件层面进一步细化到逻辑层面。redux
基于这一点优点,在后面咱们看能够看到,基于hook开发的应用里的全部组件都不会随业务增加而变得臃肿,由于在hook的世界里,状态逻辑和UI是解耦的。UI只须要消费最终计算出来的状态数据,hook只关注状态的计算和改变。api
useState组件是有状态的:缓存
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
函数是无状态的:ide
const Example = props => { const { count, onClick } = props; return ( <div> <p>You clicked {count} times</p> <button onClick={onClick}> Click me </button> </div> ); }
hooks是有状态的函数:函数
import { useState } from 'react'; const Example = () => { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
Think: useState生产出来的setter在更新state的时候不会合并,这点不一样于传统class组件的setState方法,为何这样设计?测试
// Don't do such like this ↓ const [data, setData] = useState({ count: 0, name: 'zby' }); useEffect(() => { // data: { count: 0, name: 'zby' } -> { count: 0 } setData({ count: 1 }); }, []);
咱们的应用都是从小到大发展起来的,初始充分的组件划分和状态设计是保证应用后续可维护性的重要一环,由于随着应用的扩增,组件不免变得臃肿。因此有时咱们也从一开始就一步到位引入redux之类的状态管理。
但如今,在咱们的“纯函数”组件里,每一个useState都会生产出一对儿state和stateSetter,咱们无需考虑更多的状态树的设计和组件的划分设计,逻辑代码直接从根组件写起,渐进式开发变得可行。
所谓“渐进式”开发:归纳应用的发展路径大体能够分为如下3个阶段:
1. 前期farm:
只须要把相关 state 组合到几个独立的 state 变量便可应付绝大多数状况;
2.中期gank:当组件的状态逐渐变得多起来时,咱们能够很轻松地将状态的更新交给reducer来管理(详情在下文第二章展开);
3.大后期团战:不光状态多了,状态的逻辑也愈来愈复杂的时候,咱们能够几乎0成本的将繁杂的状态逻辑代码抽离成自定义hook解决问题(详情在下文第三章展开);
基于hook,咱们的开发过程,变得比以往更有弹性。因此这样的渐进式的开发变得可行且高效。
上文说道,当组件的状态逐渐变得多起来时,咱们能够很天然的将状态的更新交给reducer来管理。
不一样于真正的redux,在实际应用中,hook带来了一种更加灵活和纯粹的模式。如今咱们能够用10行代码实现一个全局的redux,也能够用2行代码随时随地实现一个局部的redux。
A:10行代码实现一个全局redux:
import React from 'react'; const store = React.createContext(null); export const initialState = { name: '元潇' }; export function reducer(state, action) { switch (action.type) { case 'changeName': return { ...state, name: action.payload }; default: throw new Error('Unexpected action'); } } export default store;
Provider根组件挂上便可
import React, { useReducer } from 'react'; import store, { reducer, initialState } from './store'; function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <store.Provider value={{ state, dispatch }}> <div/> </store> ) }
子组件调用
import React, { useContext } from 'react'; import store from './store'; function Child() { const { state, dispatch } = useContext(store); ... }
B:随时随地实现一个局部redux
import React, { useReducer } from 'react'; const initialState = { name: '元潇' }; function reducer(state, action) { switch (action.type) { case 'changeName': return { ...state, name: action.payload }; default: throw new Error('Unexpected action'); } } function Component() { const [state, dispatch] = useReducer(reducer, initialState); ... }
上上文说道,当组件发展到必定程度,不光是状态多了,状态的逻辑也愈来愈复杂的时候,咱们能够几乎0成本的将繁杂的状态逻辑代码抽离成自定义hook解决问题。
当咱们想在两个函数之间共享逻辑时,咱们会把它提取到第三个函数中。而组件和 hook 都是函数,因此也一样适用这种方式。不一样的是,hook 是有状态的函数,它能实现以往纯函数所不能作到的更高级别的复用——状态逻辑的复用。
且先看下面两个demo示例
A:class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, name: undefined }; } componentDidMount() { service.getInitialCount().then(data => { this.setState({ count: data }); }); service.getInitialName().then(data => { this.setState({ name: data }); }); } componentWillUnmount() { service.finishCounting().then(() => { alert('计数完成'); }); } addCount = () => { this.setState({ count: this.state.count + 1 }); }; handleNameChange = name => { this.setState({ name }); }; render() { const { count, name } = this.state; return ( <div> <div> <p>You clicked {count} times</p> <button onClick={this.addCount}>Click me</button> </div> <Input value={name} onChange={this.handleNameChange} /> </div> ); } }
B:function useCount(initialValue) { const [count, setCount] = useState(initialValue); useEffect(() => { service.getInitialCount().then(data => { setCount(data); }); return () => { service.finishCounting().then(() => { alert('计数完成'); }); }; }, []); function addCount() { setCount(c => c + 1); } return { count, addCount }; } function useName(initialValue) { const [name, setName] = useState(initialValue); useEffect(() => { service.getInitialName().then(data => { setName(data); }); }, []); function handleNameChange(value) { setName(value); } return { name, handleNameChange }; } const App = () => { const { count, addCount } = useCount(0); const { name, setName } = useName(); return ( <div> <p>You clicked {count} times</p> <button onClick={addCount}>Click me</button> <Input value={name} onChange={setName} /> </div> ); };
A有2个状态:count和name,还有与之相关的一票儿逻辑,散落在组件的生命周期和方法里。虽然咱们能够将组件的state和变动action抽成公共的,但涉及到反作用的action,到最终仍是绕不开组件的生命周期。但一个组件的生命周期只有一套,不可避免的会出现一些彻底不相干的逻辑写在一块儿。如此一来,便没法实现彻底的状态逻辑复用。
在B中,咱们将count相关的逻辑和name相关的逻辑经过自定义hook,封装在独立且封闭的逻辑单元里。以往class组件的生命周期在这里不复存在。生命周期是和UI强耦合的一个概念,虽然易于理解,但它自然距离数据很遥远。而hook以一种相似rxjs模式的数据流订阅实现了组件的反作用封装,这样的好处就是咱们只须要关心数据。因此hook所带来的,毫不仅仅只是简化了state的定义与包装。
这个动画,很好的展现了A->B先后相关联的状态逻辑的组织方式变化。
这是一个商品详情页用到的SKU选择组件
咱们使用hook将原有的class组件进行重构,这个重构的过程就是一个状态逻辑的抽取过程。
咱们将组件核心的2个状态和相关的逻辑,抽到了2个独立的自定义hook中(见下图)。这样作的好处很明显,咱们的组件只须要去消费这两个hook产出的value和function,状态的维护和更新细节,已经被封装在hook里。
const { specPath, handleSpecPathChange } = useSkuSpecPath({ defaultSpecPath, dataSource }); const { skus, handleSkuAmountChange } = useSkuAmount({ defaultSelectedSkus, dataSource });
在这里提出一个自定义hook的设计范式:
const { state, handleChange, others } = useCustomHook(config, dependency?);
其中config声明了hook所须要的数据,多是内部useState的初始值,也多是结构化的数据,总结起来就是这个hook的配置
dependency一般只有hook内使用了useEffect、useCallback这类API,须要咱们声明依赖的时候须要传入。
左边是重构后的代码(脱敏代码,领会精神就好),原先核心的通用逻辑被抽到useSkuSpecPath和useSkuAmount这两个hook里后,这个组件变成了它们的调用方。后续无论UI组件如何变化和扩展,只要符合它们的接口格式约定,就能够随时随地地复用这些逻辑,很快地实现一个新的sku选择组件。
总结:自定义hook实现了状态逻辑与UI分离,经过合理抽象自定义hook,可以实现很是高级别的业务逻辑抽象复用。
推荐一个网站,里面收集了一些有意思的自定义hook
hook能够涵盖class组件的全部使用场景,同时在抽取、测试和重用代码方面提供了更大的灵活性。这就是为何Hooks表明了咱们对React将来的愿景。
react16.8以上的应用里,你们能够立马用起来了。
hook原理: Not magic,just array
let hooks, i; function useState() { i++; if (hooks[i]) { // 再次渲染时 return hooks[i]; } // 第一次渲染 hooks.push(...); } // 准备渲染 i = -1; hooks = fiber.hooks || []; // 调用组件 Component(); // 缓存 Hooks 的状态 fiber.hooks = hooks;