一、reacthooks功能介绍
1.reacthooks功能:
对函数型组件进行增强,让函数属性可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下实现相同的功能。
纯函数:相同的参数永远会返回相同的值,没有副作用。
副作用?只要不是把数据转化为视图的代码就是副作用。数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用。
2.类组件的不足:
-
缺少逻辑复用机制:
为了复用逻辑增加无实际渲染效果的组件(高阶组件),增加了组件层级,显示十分臃肿,增加了调试的难度以及运行效率的降低。 -
类组件经常会变得很复杂难以维护:
将一组相干的业务逻辑拆分到多个生命周期函数(挂载和数据更新后做相干的业务逻辑)
在一个生命周期函数内存在多个不相干的业务逻辑(挂载请求数据和监听页面变化) -
类成员方法不能保证this指向的正确性(bind 、箭头函数解决):
3.hook规则
- 只在最顶层使用Hook:不要在循环、条件或嵌套函数中调用Hook,确保总是在React函数的最顶层以及任何return之前调用他们
- 只在React函数中调用Hook:不要在普通的JavaScript函数中调用Hook。可以在React的函数组件中调用Hook,也可以在自定义Hook中调用其他Hook。
二、React Hooks 使用
1.useState:让函数组件可以保存状态(利用闭包保存数据的)
-
接受唯一的参数即状态初始值,初始值可以是任意数据类型
-
返回值是数组,数组中存储状态值和更改状态值的方法
-
方法可以被调用多次,用以保存不同状态值
-
参数可以是一个函数,函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况。
-
设置状态值方法的参数可以是一个值也可以是一个函数
-
设置状态值方法的方法本身是异步
2.useEffect:让函数组件拥有处理副作用的能力,类型生命周期函数
-
执行时机:
可以把useEffect看作componentDidMount、componentDidUpdate、componentWillUnmount的组合;
useEffect(()=>{}) ===> componentDidMount、componentDidUpdate;
useEffect(()=>{},[]) ===> componentDidMount;
useEffect(()=>()=>{}) ===> componentDidWillUnmount
React 会在组件卸载的时候执行清除操作。effect 在每次渲染的时候都会执行, React 会在执行当前 effect 之前对上一个 effect 进行清除。
-
使用示例:
监听页面滚动+定时器count++
解决的问题:
将一组相干的业务逻辑归置到了同一个副作用函数中;
简化了重复代码,使组件内部代码更加清晰(代替didMoun和didUpdate) -
只有指定数据发生变化时触发effect
-
useEffect结合异步函数
3.useReducer:另一种让函数组件保存状态的方式(与redux相似)
更改状态需要dispatch触发action,reducer函数接收到action根据类型判断决定对状态进行什么处理,最后通过返回值的方式更新状态。
什么时候使用useReducer?
组件逻辑比较复杂,在子组件里想要修改父组件状态,可以直接传递dispatch去修改
4.useContext:在跨组件层级获取数据时简化获取数据的代码
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。
5.useRef:获取DOM元素对象;保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染
6.useMemo:监测某个值的变化,根据变化值计算新值
会缓存计算结果,如果监测值没有发生变化,即使组件重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。
memo方法:性能优化,如果组件中的数据没有发生变化,阻止组件更新。类似类组件中的shouldComponentUpdate
7.useCallback:性能优化,缓存函数,使组件重新渲染是得到相同的函数实例
三、自定义hook
将共享的组件逻辑提取到可重用的函数中
- 获取数据的hook函数
- 表单属性的hook函数
四、实现
1.useState:
从使用方法入手
import React from 'react';
import ReactDOM from 'react-dom';
function useState(initValue){//初始值
let state = initValue;
function setState(newState) {//设置状态函数
state = newState;
render();//更新完状态重新渲染
}
return [state,setState]//返回数组
}
function render() {
ReactDOM.render(<App/>,document.getElementById('root'))
}
function App(){
const [count,setCount] = useState(0);
return<div>
{count}
<button onClick={()=>setCount(count+1)}>+1</button>
</div>
}
但是会发现页面中count并没有改变?因为每次改完state后重新render就会又触发useState,将count初始化成0
所以将state提到外边
简单的useState
let state;
function useState(initValue){//初始值
state = state ?? initValue;// 如果写成let state = initValue;每次render又会把state初始化成0,所以把state提出去
function setState(newState) {//设置状态函数
state = newState;
render();//更新完状态重新渲染
}
return [state,setState]//返回数组
}
使用多个useState
如果保存多个状态肯定不能用一个state,可以把所有状态存在对象或者数组里,如果存在对象里没办法传状态名称,所以保存在数组里,定义下标stateIndex确定是哪个状态。
let state = [];//存储状态值
let stateIndex = 0;//状态下标
function useState(initValue){//初始值
state[stateIndex] = state[stateIndex] ?? initValue;
let currentIndex = stateIndex;
function setState(newState){
state[currentIndex] = newState;//设置当前这个状态的值
render();
}
stateIndex ++;//每次调用下标+1
return [state[currentIndex],setState] ;//f返回数组
}
function render() {
stateIndex = 0;//重置index
ReactDOM.render(<App/>,document.getElementById('root'))
}
function App(){
const [count,setCount] = useState(0);
const [n,setN] = useState(0);
return<div>
{count}
<button onClick={()=>setCount(count+1)}>count+1</button>
{n}
<button onClick={()=>setN(n+1)}>n+1</button>
</div>
}
2.useEffect:
简单实现
let preDeps = [];
function useEffect(callback,depsAry) {
// 判断callback不是函数报错
if(Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数第一个参数必须是函数')
// 判断第二个参数是否传递
if(typeof depsAry === 'undefined'){//没传递每次都调用
callback();
}else{
// 如果不是数组报错
if(Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数第二个的参数必须是数组')
// 将当前依赖值和上一次比较
let noChanged = depsAry.every((value,index)=>value === preDeps[index])
if(!noChanged){//比较有变化
callback()
}
// 同步当前依赖值到preDeps
preDeps = depsAry
}
}
多次调用useEffect
function render() {
stateIndex = 0;//重置index
effectIndex = 0;
ReactDOM.render(<App/>,document.getElementById('root'))
}
let preDepsAry = [];//二维数组[[count],[n]]
let effectIndex = 0;//依赖下标
function useEffect(callback,depsAry) {
// 判断callback不是函数报错
if(Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数第一个参数必须是函数')
// 判断第二个参数是否传递
if(typeof depsAry === 'undefined'){//没传递每次都调用
callback();
}else{
// 如果不是数组报错
if(Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数第二个的参数必须是数组')
let preDeps = preDepsAry[effectIndex];
// 将当前依赖值和上一次比较
let noChanged = preDeps ? depsAry.every((value,index)=>value === preDeps[index]) : false;
if(!noChanged){//比较有变化
callback()
}
// 同步当前依赖值到preDeps
preDepsAry[effectIndex] = depsAry;
effectIndex++
}
}
3.useReducer:
function useReducer(reducer,initValue) {//reducer,状态初始值
const [state,setState] = useState(initValue);//
function dispatch(action){
let newState = reducer(state,action);
setState(newState)
}
return [state,dispatch]
}