useReducer
1.useReducer用最简单的话来说就是允许我们在函数组件里面像使用redux一样通过reducer和action来管理我们组件状态的变换
语法:
const [state, dispatch] = useReducer(reducer, initialArg, init?)
//useReducer和useState类似,都是用来管理组件状态的,只不过和useState的setState不一样的是,useReducer返回的dispatch函数是用来触发某些改变state的action而不是直接设置state的值,至于不同的action如何产生新的state的值则在reducer里面定义。
//useReducer接收的三个参数分别是:
//reducer: 这是一个函数,它的签名是(currentState, action) => newState,从它的函数签名可以看出它会接收当前的state和当前dispatch的action为参数,然后返回下一个state,也就是说它负责状态转换的工作。
//initialArg:如果调用者没有提供第三个init参数,这个参数代表的是这个reducer的初始状态,如果init参数有被指定的话,initialArg会被作为参数传进init函数来生成初始状态。
//init: 这是一个用来生成初始状态的函数,它的函数签名是(initialArg) => initialState,从它的函数签名可以看出它会接收useReducer的第二个参数initialArg作为参数,并生成一个初始状态initialState
案例:
import React, { useState, useReducer } from 'react'
let todoId = 1
const reducer = (currentState, action) => {
switch(action.type) {
case 'add':
return [...currentState, {id: todoId++, text: action.text}]
case 'delete':
return currentState.filter(({ id }) => action.id !== id)
default:
throw new Error('Unsupported action type')
}
}
const Todo = ({ id, text, onDelete }) => {
return (
<div>
{text}
<button
onClick={() => onDelete(id)}
>
remove
</button>
</div>
)
}
const App = () => {
const [todos, dispatch] = useReducer(reducer, [])
const [text, setText] = useState('')
return (
<>
{
todos.map(({ id, text }) => {
return (
<Todo
text={text}
key={id}
id={id}
onDelete={id => {
dispatch({ type: 'delete', id })
}}
/>
)
})
}
<input onChange={event => setText(event.target.value)} />
<button
onClick={() => {
dispatch({ type: 'add', text })
setText('')
}}
>
add todo
</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
2.useReducer +useContext实现redux
2.1 先创建一个上下文组件
import React from "react";
let ctx = React.createContext(null);
export default ctx;
2.2 自己用useReducer构建一个类似于redux的仓库,需要引入这个上下文组件让他成为提供者,将设计好的值和方法转给其他组件,这样所有组件都可以去使用和操作这个组件中的值
import React, { useReducer } from "react";
import ctx from "./store";
export default function Index(props) {
let reducer = (oldvalue, action) => {
if (action.type == "NAME") {
oldvalue.name = action.name;
}
oldvalue = JSON.parse(JSON.stringify(oldvalue));
return oldvalue;
};
let [state, dispatch] = useReducer(reducer, { name: "dsl" });
return (
<ctx.Provider value={[state, dispatch]}>{props.children}</ctx.Provider>
);
}
2.3 在index.js文件我们需要将上下文组件引入,然后将上下文组件设为根组件,将App组件插入到上下文组件的插槽中
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'
import Ctx from './store/index'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Ctx>
<App></App>
</Ctx>
);
2.4在组件中使用,需要用useContext去收上下文组件传过来的值和方法.
//App组件
import React, { useContext } from "react";
import ctx from "./store/store";
import Box from "./Box";
export default function App() {
let [state, dispatch] = useContext(ctx);
return (
<div>
{state.name}
<Box></Box>
</div>
);
}
//Box组件
import React, { useContext } from "react";
import ctx from "./store/store";
export default function Box() {
let [state, dispatch] = useContext(ctx);
let fn = () => {
dispatch({
type: "NAME",
name: "zwq",
});
};
return (
<div>
<br />
<p>BOX</p>
{state.name}
<button onClick={fn}>改变名字</button>
</div>
);
}
3.useReducer vs useState(面试)
useReducer和useState都可以用来管理组件的状态,它们之间最大的区别就是,useReducer将状态和状态的变化统一管理在reducer函数里面,这样对于一些复杂的状态管理会十分方便我们debug,因为它对状态的改变是封闭的。而由于useState返回的setState可以直接在任意地方设置我们状态的值,当我们组件的状态转换逻辑十分复杂时,它将很难debug,因为它是开放的状态管理。总体的来说,在useReducer和useState如何进行选择的问题上我们可以参考以下这些原则:
-
useState情况使用
- state的值是JS原始数据类型,如number, string和boolean等 - state的转换逻辑十分简单 - 组件内不同的状态是没有关联的,它们可以使用多个独立的useState来单独管理
-
useReducer情况使用
- state的值是object或者array - state的转换逻辑十分复杂, 需要使用reducer函数来统一管理 - 组件内多个state互相关联,改变一个状态时也需要改变另一个,放在同一个state内使用reducer来统一管理 - 状态定义在父级组件,不过需要在深层次嵌套的子组件中使用和改变父组件的状态,可以同时使用useReducer和useContext两个hook,将dispatch方法放进context里面来避免组件的props drilling - 如果你希望你的状态管理是可预测的和可维护的,请useReducer - 如果你希望你的状态变化可以被测试,请使用useReducer
自定义Hook
1.HOOk使用场景:use开头的那些官方提供的HOOK函数 只能在函数组件内部或者是自定义HOOK函数中使用,不能在类或者普通事件函数中使用
2.自定义HOOK:就是利用官网提供的HOOK来实现自己的一个具有业务功能的函数.它的特点就是使用后返回一个组件,这个思想就是类组件中的高阶组件
import React,{useState,useMemo} from 'react'
function useTool1(id){
let [flag,setflag]= useState(true)
// setflag(res)
let isLogin=useMemo(()=>{
let res=false//假装用id网络请求后端
setflag(res)
return flag
},[id])
if(isLogin){
return function(){
return (<div>
用户名:假数据
</div>)
}
}else{
return function(){
return (<div>
<a href="#">登录</a>
</div>)
}
}
}
export default useTool1;