您的当前位置:首页正文

react-hooks

2024-11-06 来源:个人技术集锦

一、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++

  • 只有指定数据发生变化时触发effect

  • useEffect结合异步函数

3.useReducer:另一种让函数组件保存状态的方式(与redux相似)

更改状态需要dispatch触发action,reducer函数接收到action根据类型判断决定对状态进行什么处理,最后通过返回值的方式更新状态。

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]

}
显示全文