在react conf 2018上,react发布了一个新的提案hook。稳定的正式版可能要等一两个月以后才能出来,目前能够在v16.7.0-alpha上试用到rfc上各类提问。css
那么这个hook究竟是个什么呢,官方的定义是这样的html
Hooks are a new feature proposal that lets you use state and other React features without writing a class.react
这是一个比class更直观的新写法,在这个写法中react组件都是纯函数,没有生命周期函数,但能够像class同样拥有state,能够由effect触发生命周期更新,提供一种新的思路来写react。(虽然官方再三声明咱们绝对没有要拿掉class的意思,但hook将来的目标是覆盖全部class的应用场景)git
其实在看demo演示的时候我是十分抗拒的,没有生命周期函数的react是个什么黑魔法,虽然代码变得干净了很多,但写法实在是发生了很大的转变,有种脱离掌控的不安全感,我甚至有点怀疑我能不能好好debug。github
演示的最后dan的结束语是这样的redux
hook表明了咱们对react将来的愿景,也是咱们用来推进react前进的方法。所以咱们不会作大幅的重写,咱们会让旧的class模式和新的hook模式共存,因此咱们能够一块儿慢慢的接纳这个新的react。api
我接触react已经四年了,第一次接触它的时候,我第一个想问的是,为何要用jsx。第二个想问的是,为何要用这个logo,毕竟咱们又不是叫atom,也不是什么物理引擎。如今我想到了了一个解释,原子的类型和属性决定了事物的外观和表现,react也是同样的,你能够把界面划分为一个个独立的组件,这些组件(component)的类型(type)和属性(props)决定了最终界面的外观和表现。讽刺的是,原子一直被认为是不可分的,因此当科学家第一次发现原子的时候认为这就是最小的单元,直到后来在原子中发现了电子,实际上电子的运动更能决定原子能作什么。hook也是同样的,我不认为hook是一个新的react特性,相反的,我认为hook能让我更直观的了解react的基本特性像是state、context、生命周期。hook能更直观的表明react,它解释了组件内部是如何工做的,我认为它被遗落了四年,当你看到react的logo,能够看到电子一直环绕在那里,hook也是,它一直在这里。数组
因而我决定干了这杯安利。安全
试了几个比较基本的api写了几个demo,代码在 github.com/lllbahol/re…, 彻底的api还请参考官方文档 reactjs.org/docs/hooks-…bash
基本的hook有三个
const [state, setState] = useState(initialState);
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
复制代码
在这里react组件就是一个简单的function
useState(initialState)也是一个函数,定义了一个state,初始值为initialState,返回值是一个数组,0为state的值,1为setState的方法。
当state发生变化时,函数组件刷新。
能够useState屡次来定义多个state,react会根据调用顺序来判断。
你必定也写过一个庞大的class, 有一堆handler函数,由于要setState因此不能挪到组件外面去,而后render函数就被挤出了页面,每次想看render都要把页面滚到底下。
如今由于useState是函数,因此它能够被挪到组件外面,连带handler一块儿,下面是一个具体一点的表单例子。
import React, { useState } from 'react';
// 表单组件,有name, phone两个输入框。
export default () => {
const name = useSetValue('hello');
const phone = useSetValue('120');
return (
<React.Fragment>
<Item {...name} />
<br />
<Item {...phone} />
</React.Fragment>
);
}
// controlled input component
const Item = ({ value, setValue }) => (
<React.Fragment>
<label>{value}</label>
<br />
<input value={value} onChange={setValue} />
</React.Fragment>
);
// 能够将state连同handler function一块儿挪到组件外面。
// 甚至能够export出去,让其余组件也能使用这个state逻辑
const useSetValue = (initvalue) => {
const [value, setValue] = useState(initvalue);
const handleChange = (e) => {
setValue(e.target.value);
}
return {
value,
setValue: handleChange,
};
}
复制代码
这个api可让你在函数组件中使用反作用(use side effects),常见的会产生反作用的方式有获取数据,更新dom,绑定事件监听等,render只负责渲染,通常会等到dom加载好以后再去调用这些反作用方法。
useEffect(didUpdate/didMount);
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
复制代码
useEffect能够接受两个参数
第一个参数为一个effect函数,effect函数在每次组件render以后被调用,至关于componentDidUpdate和componentDidMount两个生命周期之和。effect函数能够返回一个clear effect函数,会在下一次的effect函数执行以前执行,原来componentWillUnmount里执行的东西均可以交给它。调用顺序是:render(dom加载完成) => prevClearUseEffect => useEffect
第二个参数是一个数组,只有当数组传入的值发生变化时,effect才会执行。
上面的写法若是用class实现的话应该是下面这样的。咱们按时间前后将一个会产生反作用的函数的第1次调用、第2-n次调用、卸载分红3截,实际上它们老是一一对应出现的,应该是一个总体。
componentDidMount() {
this.subscription = props.source.subscribe();
}
componentDidUpdate() {
this.subscription = props.source.subscribe();
}
componentWillUnmount () {
subscription.unsubscribe();
}
复制代码
具体案例能够看一个轮播组件的demo
import React, { useState, useEffect } from 'react';
import './index.css';
const IMG_NUM = 3;
export default () => {
const [index, setIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
// 每次组件刷新时触发effect, 至关cDM cDU
if (isPlaying) {
const timeout = setTimeout(() => {
// 改变state, 刷新组件
handleNext();
}, 2000);
// 返回清除effect的回调函数, 在每次effect调用完以后,若是有则执行
return () => clearTimeout(timeout);
}
// 若是不想每次render以后都调一次effect, 可使用第二个参数做为筛选条件
}, [index, isPlaying]);
const handleNext = () => {
setIndex((index + 1) % IMG_NUM);
}
const handlePrev = () => {
setIndex((index - 1 + IMG_NUM) % IMG_NUM);
}
const handlePause = () => {
setIsPlaying(!isPlaying);
};
return (
<div>
<div className="img">{index}</div>
<button onClick={handlePrev}>prev</button>
<button onClick={handlePause}>pause</button>
<button onClick={handleNext}>next</button>
</div>
)
}
复制代码
const context = useContext(Context);
若是对react比较熟悉的话,应该用过Context这个api,用于在组件之间传递数据。useContext接受一个context对象(React.createContext生成),返回context.Consumer中得到的值。
export const Context = React.createContext(null);
function Parent() {
const someValue = 'haha';
return (
<Context.Provider value={someValue}>
<DeepTree>
<DeepChild />
</DeepTree>
</Context.Provider>
);
}
复制代码
function DeepChild() {
const someValue = useContext(Context);
return (<div>{someValue}</div>)
}
复制代码
16.7以前的Consumer写法是render props
function DeepChild() {
return (
<Context.Consumer>
{
(someValue) => <div>{someValue}</div>
}
</Context.Consumer>
)
}
复制代码
彷佛还能忍受,可是可是,为了不没必要要的刷新通常推荐用多个Context来传递刷新周期不一样的数据,所以按原来的render-props写法很容易陷入多重嵌套地狱(wrapper-hell),颇有可能你真正的渲染代码在十几个缩进后面才开始出现。继代码上下滚问题以后咱们又出现了代码左右滚问题。
<Consumer1>
{
(value1) => (
<Consumer2>
{
(value2) => (
...
)
}
</Consumer2>
)
}
</Consumer1>
// 我怎么尚未被同事打死🤦
复制代码
还有一堆高级hook
其中有一个useReducer
就是你们熟悉的那个redux里的reducer,来段模板代码让你们回忆一下。
const mapStateToProps = createStructuredSelector({
...
});
const mapDispatchToProps = (dispatch) => ({
...
});
const withReducer = injectReducer({ ... });
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(withReducer, withConnect)(Component);
复制代码
以上的这些,使用了useReducer以后都没有了。
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, {count: initialCount});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'reset'})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
复制代码
我还用useReducer实现了一个todo的demo,代码分了好几个文件就不放上来了 github.com/lllbahol/re…
除了上面提到的,还有官方罗列出来的一些时常会在写class时遇到的麻烦
用react也很久了,工程越写越复杂,组件间的数据传递是一个很大的问题,从传统的传回调函数,到跨多层多组件共享数据的时候使用redux,后来嫌模板代码太多又本身封了一层render-props结果掉进wrapper嵌套地狱的坑里,Context出来的时候开心了一下子而后发现依然在坑里。写是能写的,就是恐惧,每写一层,个人代码就又缩进了三个tab,离被同事打死又前进三步。
useContext,useReducer的用法让我想到了高阶,不一样的是能够直接用变量接住而不是挂在props上,所以不用考虑props名冲突问题,但能达到高阶一层层包裹数据的效果。
从现有的文档来看,新的api很是的多,一些是咱们熟悉的用法一些则是彻底新的东西,且暂时还没能覆盖全部生命周期场景(好比getDeriveStateFromProps),但不着急,能够一步一步来。
hook正式版发布以后我还会来更新一次这个文档,在工程里正式使用一段时间以后会再更新一次,先奶一口。