这篇文章主要介绍了React Hooks的一些实践用法和场景,遵循我我的一向的思(tao)路(是什么-为何-怎么作)html
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.vue
简单来讲,上面这段官腔大概翻(xia)译(shuo)就是告诉咱们class可以作到的老子用hooks基本能够作到,放弃抵抗吧,少年!react
其实按照我本身的见解:React Hooks是在函数式组件中的一类以use为开头命名的函数。 这类函数在React内部会被特殊对待,因此也称为钩子函数。编程
Hooks只能用于Function Component, 其实这么说不严谨,我更喜欢的说法是建议只在于Function Component使用Hooksredux
React 约定,钩子一概使用use前缀命名,便于识别,这没什么可说的,要被特殊对待,就要服从必定的规则api
Hooks做为钩子,存在与每一个组件相关联的“存储器单元”的内部列表。 它们只是咱们能够放置一些数据的JavaScript对象。 当你像使用useState()同样调用Hook时,它会读取当前单元格(或在第一次渲染时初始化它),而后将指针移动到下一个单元格。 这是多个useState()调用每一个get独立本地状态的方式数组
解决为何要使用hooks的问题,我决定从hooks解决了class组件的哪些痛点和hooks更符合react的组件模型两个方面讲述。性能优化
class组件它香,可是暴露的问题也很多。Redux 的做者 Dan Abramov总结了几个痛点:bash
- Huge components that are hard to refactor and test.
- Duplicated logic between different components and lifecycle methods.
- Complex patterns like render props and higher-order components.
第一点:难以重构和测试的巨大组件。 若是让你在一个代码行数300+的组件里加一个新功能,你不慌吗?你尝试过注释一行代码,结果就跑不了或者逻辑错乱吗?若是须要引入redux或者定时器等那就更慌了~~网络
第二点:不一样组件和生命周期方法之间的逻辑重复。 这个难度不亚于蜀道难——难于上青天!固然对于简单的逻辑可能经过HOC和render props来解决。可是这两种解决办法有两个比较致命的缺点,就是模式复杂和嵌套。
第三点:复杂的模式,好比render props和 HOC。 不得不说我在学习render props的时候不由发问只有在render属性传入函数才是render props吗?好像我再任意属性(如children)传入函数也能实现同样的效果; 一开始使用HOC的时候打开React Develops Tools一看,Unknown是什么玩意~看着一层层的嵌套,我也是无能为力。
以上这三点均可以经过Hooks来解决(疯狂吹捧~)
咱们知道,react强调单向数据流和数据驱动视图,说白了就是组件和自上而下的数据流能够帮助咱们将UI分割,像搭积木同样实现页面UI。这里更增强调组合而不是嵌套,class并不能很完美地诠释这个模型,可是hooks配合函数式组件却能够!函数式组件的纯UI性配合Hooks提供的状态和反作用能够将组件隔离成逻辑可复用的独立单元,逻辑分明的积木他不香吗!
别问,问就是文档,若是不行的话,请熟读并背诵文档...
可是(万事万物最怕But), 既然是实践,就得伪装实践过,下面就说说本人的简单实践和想法吧。
// in class component
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Hello',
age: '18',
rest: {},
}
}
...
}
// in function component
function Demo(props) {
const initialState = {
name: 'Hello',
age: '18',
rest: {},
}
const [state, setState] = React.useState(initialState)
...
}
复制代码
// 这么实现很粗糙,能够配合useRef和useCallback,但即便这样也不彻底等价于componentDidMount
function useDidMount(handler){
React.useEffect(()=>{
handler && handler()
}, [])
}
复制代码
// count更新到1就不动了
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
...
}
复制代码
其实,在class component环境下思考问题更像是在特定的时间点作特定的事情,例如咱们会在constructor中初始化state,会在组件挂载后(DidMount)请求数据等,会在组件更新后(DidUpdate)处理状态变化的逻辑,会在组件卸载前(willUnmount)清除一些反作用
然而在hooks+function component环境下思考问题应该更趋向于特定的功能逻辑,以功能为一个单元去思考问题会有一种豁然开朗的感受。例如改变document的title、网络请求、定时器... 对于hooks,只是为了实现特定功能的工具而已
你会发现大部分你想实现的特定功能都是有反作用(effect)的,能够负责任的说useEffect是最干扰你心智模型的Hooks, 他的心智模型更接近于实现状态同步,而不是响应生命周期事件。还有一个可能会影响你的就是每一次渲染都有它本身的资源,具体表现为如下几点
- 每一次渲染都有它本身的Props 和 State:当咱们更新状态的时候,React会从新渲染组件。每一次渲染都能拿到独立的状态值,这个状态值是函数中的一个常量(也就是会说,在任意一次渲染中,props和state是始终保持不变的)
- 每一次渲染都有它本身的事件处理函数:和props和state同样,它们都属于一次特定的渲染,即使是异步处理函数也只能拿到那一次特定渲染的状态值
- 每个组件内的函数(包括事件处理函数,effects,定时器或者API调用等等)会捕获某次渲染中定义的props和state(建议在分析问题时,将每次的渲染的props和state都常量化)
// 实现计数功能
const [count, setCount] = React.useState(0);
setCount(count => count + 1)
// 展现用户信息
const initialUser = {
name: 'Hello',
age: '18',
}
const [user, setUser] = React.useState(initialUser)
复制代码
// 修改上面count更新到1就不动了,方法1
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
...
}
// 修改上面count更新到1就不动了,方法2( 与方法1的区别在哪里 )
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
...
}
复制代码
关于useEffect, 墙裂推荐Dan Abramov的A Complete Guide to useEffect,一篇支称整篇文章架构的深度好文!
/** 修改需求:每秒不是加多少能够由用户决定,能够看做不是+1,而是+step*/
// 方法1
function Counter() {
const [count, setCount] = React.useState(0);
const [step, setStep] = React.useState(1);
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
...
}
// 方法2( 与方法1的区别在哪里 )
const initialState = {
count: 0,
step: 1,
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { ...state, count: count + step };
} else if (action.type === 'step') {
return { ...state, step: action.step };
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
...
}
复制代码
说这个以前,先说一说若是你要在FP里面使用函数,你要先要思考有替代方案吗?
方案1: 若是这个函数没有使用组件内的任何值,把它提到组件外面去定义
方案2:若是这个函数只是在某个effect里面用到,把它定义到effect里面
若是没有替代方案,就是useCallback出场的时候了。
// 场景1:依赖组件的query
function Search() {
const [query, setQuery] = React.useState('hello');
const getFetchUrl = React.useCallback(() => {
return `xxxx?query=${query}`;
}, [query]);
useEffect(() => {
const url = getFetchUrl();
}, [getFetchUrl]);
...
}
// 场景2:做为props
function Search() {
const [query, setQuery] = React.useState('hello');
const getFetchUrl = React.useCallback(() => {
return `xxxx?query=${query}`;
}, [query]);
return <MySearch getFetchUrl={getFetchUrl} />
}
function MySearch({ getFetchUrl }) {
useEffect(() => {
const url = getFetchUrl();
}, [getFetchUrl]);
...
}
复制代码
// 存储不变的引用类型
const { current: stableArray } = React.useRef( [1, 2, 3] )
<Comp arr={stableArray} />
// 存储dom引用
const inputEl = useRef(null);
<input ref={inputEl} type="text" />
// 存储函数回调
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}
复制代码
// 此栗子来自文档
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
复制代码
// 此栗子来自文档
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
复制代码
说是彩蛋,实际上是补充说明~~
hooks除了要以use开头,还有一条很很很很重要的规则,就是hooks只容许在react函数的顶层被调用(这里墙裂推荐Hooks必备神器eslint-plugin-react-hooks)
考虑到出于研(gang)究(jing)精神的你可能会问,为何不能这么用,我偏要的话呢?若是我是hooks开发者,我会坚决果断地说出门右转,有请下一位开发者!固然若是你想知道为何这么约定地话,仍是值得探讨一下的。其实这个规则就是保证了组件内的全部hooks能够按照顺序被调用。那么为何顺序这么重要呢,不能够给每个hooks加一个惟一的标识,这样不就能够随心所欲了吗?我之前一直都这么想过直到Dan给了我答案,简单点说就是为了hooks最大的闪光点——custom-hooks
给个人感受就是custom-hooks是一个真正诠释了React的编程模型的组合的魅力。你能够不看好它,但它确实有过人之处,至少它呈现出思想让我越想越上头~~以致于vue3.0也借鉴了他的经验,推出了Vue Hooks。反手推荐一下react conf 2018的custom-hooks。
// 修改页面标题
function useDocumentTitle(title) {
useEffect (() => {
document.title = title;
}, [title]);
}
// 使用表单的input
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
};
}
复制代码
最后抛出两个讨论的小问题。
React Hooks没有缺点吗?
- 确定是有的,给我最直观的感觉就是使人又爱又恨的闭包
- 不断地重复渲染会带来必定的性能问题,须要人为的优化
上面说了写了不少的setInterval的代码,能够考虑封装成一个custom-hooks?
- 能够考虑封装成useInterva,关于封装仍是墙裂推荐Dan的 Making setInterval Declarative with React Hooks
- 若是有一堆特定的功能hooks,是否是彻底能够经过组装各类hooks完成业务逻辑的开发,例如网络请求、绑定事件监听等
本人能力有限,若是有哪里说得不对的地方,欢迎批评指正!
真的真的最后,怕你错过,再次安利Dan Abramov的A Complete Guide to useEffect,一篇支称整篇文章架构的深度好文!