共计 8641 个字符,预计需要花费 22 分钟才能阅读完成。
React Hooks 是 React v16.8 中新增加的个性,这个个性次要是对函数型组件的性能进行加强,让函数型组件能够做类组件做的事件。
这个篇文章次要蕴含三个局部:
-
什么是 react hooks
- react hooks 次要蕴含哪些性能,要解决哪些问题。
-
react hooks 的应用
- react hooks 到底是什么
- 这些函数要在什么场景下应用
-
自定义 react hooks
- 通过本人定义函数,如何将组件外部的公共逻辑提取进去
1. 什么是 React Hooks
1.react hooks 性能介绍
对函数组件进行加强,让函数组件能够贮存状态,能够领有解决副作用的能力,让开发者在不应用类组件的状况下,实现雷同的性能。
副作用:在组件中不是将数据转换为视图的代码被称为副作用。
比方:获取 DOM 元素、为 DOM 元素增加事件、设置定时器以及发送 Ajax 申请等,这都属于副作用。
2. 类组件的有余
-
短少逻辑复用机制
- 为了复用逻辑减少无理论渲染成果的组件,减少了组件层级显示非常臃肿,减少了调试的难度以及运行效率的升高。
-
类组件常常会变得很简单难以保护
- 将一组相干的业务逻辑拆分到多个生命周期函数中,例如:1. 在组件挂载时要做些什么 2. 更新实现之后要做些什么
- 在一个生命周期函数内存在多个不相干的业务逻辑
- 类成员的办法不能保障 this 指向的正确性,例如:在申明一些函数要应用 bind 进行绑定,或者嵌套函数来解决 this 指向的问题
2. React Hooks 的应用
Hooks 意为钩子,React Hooks 就是一堆钩子函数,React 通过这些钩子函数对函数组型件进行加强,不同的钩子函数提供了不同的性能。
react hooks 提供的钩子函数有:
- useState()
- useEffect()
- useReducer()
- useRef()
- useCallback()
- useContext()
- useMemo()
1.useState
用于为函数组件引入状态,在现有的认知外面,在函数执行结束之后该变量就会被开释掉,所以函数型组件本来是不能够保留组件数据的,useState 用来解决这个问题
import React, {useState} from 'react';
function App () {
// useState 返回一个数组,count: 状态 setCount: 设置状态的办法
const [count, setCount] = useState(0);
return <div>
<span>{count}</span>
<button onClick={() => setCount(count => count + 1)}>+1</button>
</div>
}
应用 setCount 办法设置 count 的值后,组件会被从新渲染,而渲染后 count 的状态还在。
-
useState 应用细节
- 接管惟一的参数即状态初始值,初始值能够是任意数据类型。
- 返回为数组,数组中存储状态值和更新状态值的办法,办法名称约定以 set 结尾,前面加上状态的名称。
- useState 办法能够被调用屡次,用以保留不同的状态值。
- 参数能够是一个函数,函数返回什么初始状态值就是什么,函数只会被调用一次,用在初始值是动静值的状况下。
import React, {useState} from 'react';
function UseState (props) {console.log('渲染即执行');
// 传入一个办法
const [count, setCount] = useState(() => {console.log('这个地位只会被执行一次');
return props.count || 0;
});
// 能够接管任意数据类型为状态值
const [person, setPerson] = useState({name: 'howie', age: 20});
return <div>
<span>{count} {person.name} {person.age}</span>
<button onClick={() => setCount(count + 1)}>plus</button>
<button onClick={() => setPerson(() => ({...person, name: 'zs'}))}>change person</button>
</div>
}
export default UseState;
-
设置状态的细节
- 设置状态值的办法的参数,能够是一个值也能够是一个函数
- 设置状态值的办法自身是异步的
import React, {useState} from 'react';
function UseState (props) {const [count, setCount] = useState(() => {return props.count || 0;});
function handleSetCount () {
// 传递回调函数设置状态,setCount 是异步的
setCount(count => {
return count + 1;
// 这样设置 title 就会变成同步的
// const newCount = count + 1;
// document.title = newCount; // 1
// return newCount;
});
document.title = count; // 0
}
return <div>
<span>{count}</span>
<button onClick={handleSetCount}>plus</button>
</div>
}
export default UseState;
2.useReducer
useReducer 是另一种让函数组件保留状态的形式。
import React, {useReducer} from 'react';
function UseReducer () {function reducer(state, action) {switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
const [count, dispatch] = useReducer(reducer, 0);
return <div>
<p>useReducer</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<span>{count}</span>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
}
export default UseReducer;
相较于 useState 的劣势在于,如果子组件要批改父组件中的数据时,能够间接将父组件中的 dispatch 办法传递给子组件,
而后依据不同的类型,做不同的解决。
3.useContext
在跨组件层级获取数据时简化获取数据的代码。
-
跨层级获取数据
import React, {createContext} from 'react'; const countContext = createContext(); function UseContext () { return <div> <header>UseContext</header> <countContext.Provider value={100}> <Foo/> </countContext.Provider> </div> } function Foo () { return <countContext.Consumer> { value => {return <div>{value}</div> } } </countContext.Consumer> } export default UseContext;
-
简化代码
import React, {createContext, useContext} from 'react'; const countContext = createContext(); function UseContext () { return <div> <header>UseContext</header> <countContext.Provider value={100}> <Foo/> </countContext.Provider> </div> } function Foo () {const value = useContext(countContext); return <div>{value}</div> } export default UseContext;
4.useEffect
让函数组件领有解决副作用的能力,相似生命周期函数。
1. useEffect 执行机会
- useEffect(() => {}): componentDidMount、componentDidUpdate
- useEffect(() => {}, []): componentDidMount
- useEffect(() => () => {}): componentWillUnMount
能够把 useEffect 看做 componentDidMount、componentDidUpdate、componentWillUnMount 这三个生命周期函数的组合。
import React, {useEffect, useState} from "react";
function UseEffect() {const [count, setCount] = useState(0);
// 组件挂载和组件更新时执行
// useEffect(() => {// console.log('run')
// });
// 组件挂载时执行
// useEffect(() => {// console.log('run')
// }, []);
// 革除上一个 effect
useEffect(() => {console.log('1')
return () => {console.log('run');
}
});
return <div>
<header>useEffect</header>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>plus</button>
</div>
}
export default UseEffect;
2. useEffect 应用形式
- 为 window 对象增加滚动事件
- 设置定时器让 count 数值每隔一秒减少 1
import React, {useEffect, useState} from "react";
import {root} from "../index";
function UseEffect() {function onScroll() {console.log('页面滚动了');
}
// 挂载之后绑定事件
useEffect(() => {window.addEventListener('scroll', onScroll)
return () => {console.log('run')
window.removeEventListener('scroll', onScroll);
}
}, []);
const [count, setCount] = useState(0);
useEffect(() => {const timer = setInterval(() => {setCount(count => count + 1);
}, 1000)
return () => {clearInterval(timer);
}
}, [])
useEffect(() => () => {console.log('卸载组件');
})
return (
<div>
<header>useEffect</header>
<div>{count}</div>
<button onClick={() => root.unmount()}> 卸载组件 </button>
</div>
)
}
export default UseEffect;
3. useEffect 解决的问题
- 依照用处将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
- 简化反复代码,是组件外部代码更加清晰(在类组件中通常 componentDidMount 和 componentDidUpdate 中的代码是一样的)
4. useEffect 数据监测
只有指定数据发生变化的时候才会去触发 effect
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
// 只有 count 扭转的时候才会执行
useEffect(() => {document.title = count;}, [count]);
return (
<div>
<header>useEffect</header>
<span>{count} {num}</span>
<button onClick={() => setCount(count => count + 1)}>addCount</button>
<button onClick={() => setNum(count => count + 1)}>addNum</button>
</div>
)
5.useEffect 联合异步函数
useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise
useEffect(() => {
// 编写一个立刻执行函数
(async () => {await axios.get()
})();})
6.useMemo
useMemo 的行为相似 Vue 中的计算属性,能够监测某一个值的变动,依据变动值计算新值。
useMemo 会缓存计算结果,如果监测值没有发生变化,即便组件从新渲染也不会从新计算,此行为能够有助于防止在每个渲染上进行低廉的计算。
import {useMemo} from 'react';
// 挂载实现后默认执行一次,如果监听的 count 产生了变动则再次执行
const result = useMemo(() => {return result;}, [count]);
7. 应用 memo 办法进步组件性能
性能优化,如果本组件中的数据没有产生任何变动,就阻止组件进行更新。相似类组件中的 PureComponent 和 shouldComponentUpdate
import React, {memo, useState} from "react";
function Memo () {const [count, setCount] = useState(0);
return <div>
<span>{count}</span>
<button onClick={() => setCount(count => count + 1)}>+1</button>
<Foo/>
</div>
}
// 每当 count + 1 Foo 即便没有扭转也会被从新渲染
// function Foo() {// console.log('从新渲染');
// return <div>Foo 组件 </div>
// }
// 这样 Memo 组件更新就不会连带 Foo 更新
const Foo = memo(() => {console.log('从新渲染');
return <div>Foo 组件 </div>
})
// 当 Memo 没有产生数据变动且父组件产生了变动,Memo 组件不会被更新
export default memo(Memo);
8.useCallback
性能优化,缓存函数,使组件从新渲染时失去雷同的函数实例。
Foo 子组件被从新渲染
import React, {useCallback, useState, memo} from 'react';
function UseCallback() {const [count, setCount] = useState(0);
function resetCount () {setCount(0);
}
return <div>
<span>{count}</span>
<button onClick={() => setCount(count => count + 1)}>+1</button>
<Foo resetCount={resetCount}/>
</div>
}
const Foo = memo((props) => {
/**
* 当 count 产生扭转时会更新父组件,父组件中的 resetCount 函数被从新定义
* 这时的 resetCount 与 Foo props 中的 resetCount 实例曾经产生了扭转,所以 Foo 会被从新渲染
*/
console.log('从新渲染');
return <div>Foo 组件
<button onClick={props.resetCount}>resetCount</button>
</div>
})
export default UseCallback;
// 应用 useCallback 优化 resetCount
const resetCount = useCallback(() => setCount(0), [setCount]);
9.useRef
1. 获取 DOM 对象
import React, {useRef} from 'react';
function UseRef () {const userName = useRef();
const handler = () => console.log(userName.current);
return <div>
<header>UseRef</header>
<input ref={userName} onChange={handler}/>
</div>
}
export default UseRef;
2.useRef 保留数据
即便组件从新渲染,保留的数据依然还在,保留的数据被更改不会触发组件渲染(跨组件周期)。
// 保留数据
const [count, setCount] = useState(0);
// 即便组件更新并不会重置,所以 stopCount 能够拿到 timer
let timer = useRef();
useEffect(() => {timer.current = setInterval(() => {setCount(count => count + 1);
}, 1000);
}, [])
const stopCount = () => {clearInterval(timer.current);
}
return <div>
<header>useRef</header>
<span>{count}</span>
<button onClick={stopCount}>stop</button>
</div>
10. 自定义 hook
- 自定义 hook 是规范的封装和共享逻辑的形式
- 自定义 hook 是一个函数,其名称以 use 结尾
- 自定义 hook 其实就是逻辑和内置 hook 的组合
import React, {useEffect, useState} from 'react';
import axios from 'axios';
/**
* 以后组件须要再组件挂载实现之后,获取文章数据
* 假如获取文章数据的申请是共享逻辑,尝试把它写到自定义 hook 当中
*/
// 创立自定义 hook
function useGetEssay () {const [essay, setEssay] = useState({});
useEffect(() => {axios.get('https://www.fastmock.site/mock/eefbb8ce7302645510629510865adb64/api/essay')
.then(res => setEssay(res.data));
}, []);
return [essay, setEssay];
}
function CustomizationHook () {const [essay, setEssay] = useGetEssay(null);
return <div>
<header>CustomizationHook</header>
<div>
<p>{essay.title}</p>
<div>{essay.content}</div>
</div>
</div>
}
export default CustomizationHook;
-
提取表单中的罕用逻辑
function useUpdateInput(initialValue) {const [value, setValue] = useState(initialValue); return { value, onChange: e => setValue(value => value = e.target.value) } } function CustomizationHook() {const userNameInput = useUpdateInput(''); const passwordInput = useUpdateInput(''); const submitForm = e => {e.preventDefault(); console.log(userNameInput.value, passwordInput.value) } return <form onSubmit={submitForm}> {/* 为了绑定数据,每个表单元素都须要设置 value 和 onChange,这就属于共享逻辑 */} <input type="text" name="username" {...userNameInput} /> <input type="text" name="password" {...passwordInput} /> <input type="submit"/> </form> }