"Unlearn what you have learned" -- Yodajavascript
有一天我在逛Medium的时候,忽然发现了一篇介绍React Hooks的文章,我认真看了一遍后,计划好好了解一下它。html
在学习Hooks以前,官网上说了Hooks是彻底可用的(v16.8.0),并无破坏性变动,并且彻底向后兼容,与其说是一种新API,不如说是React Team他们把React更核心的操做数据与UI的能力挖掘了出来。java
嗯美滋滋~学完应该能够在工做项目里用了!开始学习吧!react
其实Hooks主要经常使用的能够有如下几个:redux
useState
useEffect
useContext
useMemo
useRef
useReducer
useCallback
列举的以上这几个,其实已经算是比较经常使用的,尤为是前两个,接下来就会介绍它们部分几个的使用。数组
useState
这个钩子其实对应的是咱们以前class Component
里的this.setState
。缓存
useState
传参表明默认值,能够是原始值,也能够是对象、数组,因此其实表达能力很丰富。useState
调用后返回是一对值,对应当前的值和更新这个值的函数,用数组解构的方式获取很简洁。useState
在一个函数组件里能够屡次使用。useState
和this.setState
区别之处在于,前者每次更新后state
都是新值,换而言之实际上是不可变数据的概念。然后者使用后,其实更新state
部分的值,引用自己并没有改变。简单使用以下示例。bash
import React, { useState } from 'react';
export default function StateHook() {
const [count, useCount] = useState(0);
return (
<> <p>You clicked {count} times</p> <button onClick={() => useCount(count + 1)}>Click me</button> </> ); } 复制代码
useEffect
这个钩子势必是咱们经常使用的。架构
componentDidMount
和componentDidUpdate
的这两个生命周期钩子组合的效果。那么它的调用时机大概是每次渲染结束后,因此不会阻塞组件渲染。useEffect
通常用于实现设置数据请求、监听器等有反作用的功能,传入的第一个参数函数A1用于设置反作用,而是传入的这个函数能够返回一个函数A2用于取消函数A1的反作用。这两个函数的React调用它们时机分别在于,注册反作用的函数A1在当次渲染结束后当即执行,取消反作用的函数A2在下次渲染开始以前当即执行。再次强调,这么设计的理由仍是为了避免阻塞组件渲染。useEffect
第二个参数用于设置反作用的依赖数组。什么意思?思惟灵活的同窗已经想到了,若是每次渲染都执行反作用,有可能形成性能浪费,那么能够经过告诉React,这个钩子依赖某些props
或者states
,在这些依赖不发生改变时,这个反作用不会再重复执行。在如下的例子中,能够传空数组,告诉React该反作用什么也不依赖,那么它只会在第一次渲染时执行一次(可是通常不推荐这么作)。若是不传第二个参数,则意味着每次渲染都必然执行一次,此时应当注意内存泄露。useEffect
后,一个反作用的注册监听与对应的取消注册逻辑所有放在了一块儿,对比与以往的分别在componentDidMount
、componentDidUpdate
、componentWillUnmount
里分散同一反作用的逻辑。useEffect
的使用更有吸引力和说服力了。import React, { useState, useEffect } from 'react';
export default function EffectHook({ dep }) {
const [width, setWidth] = useState(window.innerWidth);
function handleWindowResize() {
const w = window.innerWidth;
setWidth(w);
}
useEffect(() => {
window.addEventListener('resize', handleWindowResize);
return () => {
window.removeEventListener('resize', handleWindowResize);
};
},
// deps
[]
);
return (
<> <p>window.innerWidth: {width}</p> </> ); } 复制代码
这个钩子仍是和原有的Context.Provider
、Context.Consumer
同样的理解便可。用法示例以下,理解方便,再也不赘述。app
import React, { useContext } from 'react';
export const souliz = {
name: 'souliz',
description: 'A normal human named by his cat.'
};
export const UserContext = React.createContext(souliz);
export default function ContextHook() {
const context = useContext(UserContext);
return (
<>
<p>UserContext name: {context.name}</p>
<p>UserContext description: {context.description}</p>
</>
);
}
复制代码
有时候咱们会遇到一个极耗性能的函数方法,但因为依赖了函数组件里一些状态值,又不得不放在其中。那么若是咱们每次渲染都去重复调用的发,组件的渲染必然会十分卡顿。
所以写了如下示例验证,一个计算斐波那契的函数(众所周知的慢),读者能够拷贝这段代码,注释useMemo
那一行,使用直接计算来看,点击按钮触发组件从新渲染,会发现很卡顿(固然了),那么此时useMemo
做用就发挥出来了,其实理解上仍是和原有的React.memo
同样,可用于缓存一下计算缓慢的函数,若是依赖没有发生改变,则重复使用旧值。前提必然是这个函数是一个纯函数,不然必然会引起问题。
(useCallback
其实也和useMemo
道理相似,不过它解决的问题其实若是依赖不改变,使用旧的函数引用,在useEffect
的依赖是函数时,可使用useCallback
的特性来避免重复触发反作用的发生,所以再也不赘述useCallback
)
import React, { useState, useMemo } from 'react';
let fib = n => (n > 1 ? fib(n - 1) + fib(n - 2) : n);
let renders = 0;
export default function MemoHook() {
const defaultInput = 37;
const [input, setInput] = useState(defaultInput);
const [time, setTime] = useState(0);
const value = useMemo(() => fib(input), [input]);
// 来来来,看看不使用Memo的后果就是卡顿
// const value = fib(input);
return (
<>
<p>fib value is {value}</p>
<input
type="number"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={() => setTime(time + 1)}>Trigger render {time}</button>
<footer>render times: {renders++}</footer>
</>
);
}
复制代码
useRef
这个钩子须要更通用的理解方式,不一样于咱们以前使用的React.createRef()
,这个钩子用于建立的是一个引用对象,那么能够用于突破useState
所带来的局限。什么意思呢?useState
每次渲染都是新的值,也就是下面示例中,若是我点击3次按钮,分别更新了值触发5次组件从新渲染,那么经过延时5秒后获取current值如示例二,若是须要在某些操做中获取组件最新的某些state
是最新的值的时候,useRef
能够派上大用场。
import React, { useRef, useEffect, useState } from 'react';
export default function RefHook() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
latestCount.current = count;
useEffect(() => {
setTimeout(() => {
console.log(`Ref: You clicked ${latestCount.current} times`);
console.log(`state: You clicked ${count} times`);
}, 5000);
});
return (
<> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </> ); } 复制代码
Ref: You clicked 3 times
state: You clicked 1 times
Ref: You clicked 3 times
state: You clicked 2 times
Ref: You clicked 3 times
state: You clicked 3 times
复制代码
相信同窗们都使用过redux,React Team考虑到这种使用方式常见,因而设计出来了这么一个钩子。这样的话其实解决了咱们常见写redux的多文件跳跃编写的烦恼,并且十分易于理解。(固然还有比较高级的用法)。如下代码示例。
import React, { useState, useReducer } from 'react';
const defaultTodos = [
{
id: 1,
text: 'Todo 1',
completed: false
},
{
id: 2,
text: 'Todo 2',
completed: false
}
];
function todosReducer(state, action) {
switch (action.type) {
case 'add':
return [
...state,
{
id: Date.now(),
text: action.text,
completed: false
}
];
case 'complete':
return state.map(todo => {
if (todo.id === action.id) {
todo.completed = true;
}
return todo;
});
default:
return state;
}
}
export default function ReducerHook() {
const [todos, dispatch] = useReducer(todosReducer, defaultTodos);
const [value, setValue] = useState('');
function handleTextChange(e) {
setValue(e.target.value);
}
function handleAddTodo() {
if (value === '') {
return;
}
dispatch({
type: 'add',
text: value
});
setValue('');
}
function handleCompleteTodo(id) {
dispatch({
type: 'complete',
id
});
}
return (
<>
<section>
<input
type="text"
onChange={handleTextChange}
value={value}
/>
<button onClick={handleAddTodo}>Add Todo</button>
</section>
<ul className="todos">
{todos.map(todo => (
<ol id={todo.id} key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<input
type="checkbox"
disabled={todo.completed}
onClick={() => handleCompleteTodo(todo.id)}
/>
</ol>
))}
</ul>
</>
);
}
复制代码
其实useReducer
的原理大概也能够这么来实现。
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
复制代码
相信学完这些Hooks的使用后,许多同窗都是心里充满了不少疑惑的同时也想要尝试看看怎么使用到实际项目了。
固然如今React官方的建议是:
class Component
这些原有API的。eslint-plugin-react-hooks
,用于检测对于Hooks的不正当使用。(据说create-react-app很快将会加上这个配置)传统组件的开发有如下几个局限:
写到这里,文章篇幅已经很长了。一篇文章是说不完Hooks的。学习Hooks的最推荐的实际上是看官网文档以及Dan Abramov的博文,以及多多动手实践。
谢谢你们阅读~~