持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
React Hooks介绍
对函数组件增强,可以存储状态,处理副作用的能力 让函数组件可以实现类组件相同功能
为什么要使用hooks
- 类组件缺少逻辑复用
- 相同逻辑分布在多个生命周期中
- 同一个生命周期存在不同逻辑代码
- 类组件要考虑this指向,hooks不用
缺点,相对于class组件
重要
:每次数据更新,定义在方法组件中的变量函数都会重新定义 真是因为有这个缺点导致专门实现了缓存变量的的钩子 useRef 和缓存方法的钩子useCallback 后面会详细介绍
使用
钩子函数
useState(parameter) 引入state状态
唯一参数,设置默认值, parameter可以为任意类型包含函数。 只会执行一次,而组件定义的变量每次都运行,所以尽量都放在parameter形式直接赋值
const [count, setCount] = useState(0)
// 对象
const [person, setPerson] = useState({name: '张三', age: 23})
function increase() {
// 回调函数的参数就是 原来的count的值
setCount(count => {
return count + 1
})
}
复制代码
注意 setState(setCount) 是异步的
useReducer()函数
也是让组件保存状态的, 主要用在是子组件想改变父组件状态的值,我们可以直接传递dispatch就可以更改, 而不像类组件传递方法
使用,参见useContext使用的App组件
useContext()
跟类组件context使用方法一致
import React, {createContext, useContext} from 'react';
// 1. 定义context
const countContext = createContext()
// 2. 父类provider 想要共享给所有子类的数据 <countContext.Provider value={count}>
function App() {
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 (
<countContext.Provider value={count}>
{count}
<Header/>
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
</countContext.Provider>
)}
// 4. useContext获取数据
function Header() {
const count = useContext(countContext)
return (
<div>count: {count}</div>
)
}
复制代码
useEffects()
看做三个生命周期函数的集合
componentDidMount : 挂载组件,页面显示只会调一次componentDidUpdate : 页面使用到的数据变更,都会执行
componentWillUnmount : 卸载组件
useEffect(() => {}) => componentDidMount, 第二个参数没有指定相当于所有参数变更都执行即 === componentDidUpdate
useEffect(() => {}, []) => componentDidMount 第二个参数为空,任何参数变更都不会执行 即componentDidUpdate不可能执行
useEffect(() => () => {}) => componentWillUnMount 返回的方法体就是组件卸载的时候会走
复制代码
指定参数变更才会执行componentDidUpdate
第二个参数限定,指定参数变更componentDidUpdate才会执行,不设置第二个参数,则默认所有参数变更都会执行
useEffect(() => {
// componentDidMount 和 componentDidUpdate都会执行
})
用第二个参数
useEffect(() => {
// componentDidMount 和 count变更才会执行componentDidUpdate
}, [count])
useEffect(() => {
// componentDidMount 和 count变更才会执行componentDidUpdate
// 返回一个函数,函数体就是componentWillUnMount
return () => {
//componentWillUnMount
}
}, [count])
复制代码
useEffect 可以多次调用,不同逻辑可以单独写一个useEffect 简化重复代码
执行异步操作
需用函数自执行方式
useEffect(() => {
(async () => {
await requestData()
})()
})
复制代码
useMemo()
相当于vue的computed 也和mobx中的computed类似
简单说就是如果某个(或多个)变量变化,会根据次变量计算一个新值返回 发散: class组件render函数前面的逻辑代码可以放在其中,减少执行次数。
const newValue = useMemo(() => {
return result
}, [count]) // 第二个参数变更就是引起改方法执行的因素
复制代码
memo方法
类组件中的pureComponent 和 shouldComponentUpdate 即React自动判断 如果参数没变化,则不重新渲染组件
function Counter() {
}
export default memo(Counter)
复制代码
useCallback()
性能优化, 缓存函数, 使组件重新渲染时得到相同的函数实例. 因为方法定义在方法组件里面,当组件渲染的时候,方法重新实例化,导致两次的方法不是一致的
function App() {
const [count, setCount] = userState(0);
// 每次count变化 resetCount都跟上次的不一致,导致渲染了Header组件。
const resetCount = () => {
setCount(0);
}
return (
<countContext.Provider value={count}>
{count}
<Header resetCount={resetCount}/>
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
</countContext.Provider>
)}
const Header = memo(function Header(props) {
const count = useContext(countContext)
return (
<div>
count: {count}
<div onClick={props.resetCount}>清空count</div>
</div>
)
})
复制代码
将resetCount函数改造如下,就可以保证每次得到的方法实例是一致的
const resetCount = useCallback(() => {
setCount(0);
},[setCount]) // 同样第二个参数是setCount函数变化才会重新实例化resetCount
复制代码
useRef()
1. 获取dom
useRef()=== createRef()
使用
const inputRef = useRef()
<input ref={inputRef}/>
复制代码
2. 缓存数据
方法组件方便的同时也造成了不便,就是每次数据更新,定义的变量函数都会重新定义。
function App() {
const [count, setCount] = userState(0);
const timerId = null
// 组件挂载后启动定时器每秒加一
useEffect(() => {
timerId = setInterval(() => {
setCount(count => count + 1)
})
}, [])
const stopInterval = () => {
// 永远都停不下来,因为count变化timerId重新赋值null 永远清空不了
clearInterval(timerId)
}
return (
<countContext.Provider value={count}>
{count}
<button onClick={stopInterval}>-1</button>
</countContext.Provider>
)}
复制代码
解决方法
// 1. 用useRef() 来定义timerId
let timerId = useRef();
//2. 赋值和取值的时候用timerId.current
useEffect(() => {
timerId.current = setInterval(() => {
setCount(count => count + 1)
})
}, [])
const stopInterval = () => {
clearInterval(timerId.current)
}
复制代码
自定义Hook函数
为了在组件之间实现逻辑共享,我们可以自定义hook,实际就是逻辑和内置hook的组合 就是一个函数,名称以use开头
其实很容易
/**
* 自定义hook,在组件加载的时候获取appInfo信息
* @returns {*[]}
*/
function useAppInfo() {
const [appInfo, setAppInfo] = useState({})
useEffect(()=> {
const info = JSON.parse(localStorage.getItem('appInfo')|| "{}")
setAppInfo(appInfo)
}, [])
return [appInfo, setAppInfo]
}
// 使用
const [appInfo, setAppInfo] = useAppInfo()
复制代码
input组件封装 onChange方法和value值
function useUpdateInput(initialValue) {
const [value, setValue] = useState(initialValue);
return {
value,
onChange: event => setValue(event.target.value)
}
}
// 使用
function FormTest() {
const useNameInput = useUpdateInput('')
const submitForm = event => {
console.log(useNameInput.value)
}
return <form onsubmit={submitForm}>
<input {...useNameInput}/>
</form>
}
<input type="text" name="useName" />
复制代码
路由的Hooks
安装包 react-router-dom
import {useHistory, useLocation, useRouterMatch, useParams} from 'react-router-dom'
const history = useHistory()
复制代码
源码实现
源码重点是解决多次使用useState互相不受影响的问题
实现useState()钩子
多次使用useState()
的时候,我们用数组index
保存对应的value
和setValue
方法
let state = [];
let setters = [];
let stateIndex = 0;
function createSetter (index) {
return function (newState) {
state[index] = newState;
render ();
}
}
function useState(initialState) {
// 首先赋值,如果有值直接取值,没值给默认值
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
setters.push(createSetter(stateIndex))
stateIndex++
return [state[stateIndex], setters[stateIndex]]
}
function render () {
stateIndex = 0;
effectIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
复制代码
主要思想,通过array[index]
来分别保存多次调用的值,和setState
方法 而确定index
问题需要用闭包来保持不被释放,createSetter
就是解决该问题。 在调用setState
方法后,也就是const setState = createSetter()
返回的函数。 调用render
方法来更新UI
实现useEffect()钩子
// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;
function useEffect(callback, depsAry) {
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('第一个参数必须是函数');
// 第二个参数如果没有传,那么每次都会调callback()方法
if (typeof depsAry === 'undefined') {
callback();
} else {
if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('第二个参数必须是数组');
// 获取上一次的状态值
let prevDeps = prevDepsAry[effectIndex];
// 遍历数组,对比上次值如果有一个不一样,那么就调用callback()
let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
if (hasChanged) {
callback();
}
// 同步依赖值
prevDepsAry[effectIndex] = depsAry;
effectIndex++;
}
}
复制代码
至此Hooks全部完结