说说useReducer

useReducer 是什么

使用方法

useReduceruseState 的替代方案。可以通过下面的源码,看下 useStateuseReducer 传参和返回值的类型。

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}
const [state, setState] = useState(initialState);

const [state, dispatch] = useReducer(reducer, initialArg, init);

useReducer 接收三个参数:

  • reducer —— 是函数 (state, action) => newState
    • state 是当前最新的状态值,action 用于告诉 reducer 当前执行的操作,reducer 会根据不同操作执行不同逻辑去更新 state
    • reducer 是一个纯函数,没有任何 UI 和副作用
  • initialArg —— 是指定的初始 state
  • init —— 是函数 (initialArg) => initialState
    • 这个参数是可选的,如果传入了 init,初始 state 就是 init(initialArg) 的返回结果

useReducer 返回一个数组:

  • 第一项是 state
  • 第二项是 dispatch,是函数 (action) => void
    • 通过调用 dispatch 传入 action,来告诉 reducer 执行什么操作,然后更新 state
    • dispatch 不会在组件重新渲染时改变

可以通过下面这个计数器的栗子,直观地看下怎么使用 useReducer

const initializer = (initialValue) => {
  return {
    count: initialValue.count + 10,
  };
};

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState, initializer);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

因为 useReducer 传入了第三个参数 initializer,所以初始 state 不是 0,而是计算后的 10。
在这里插入图片描述

和 Redux 的关系

Store, Reducer, ActionRedux 的三核心概念,同时也是 useReducer 的三大核心概念。
通过调用 useReducer 返回的 dispatch,传入 action 来告诉 reducer 要执行的操作,然后 reducer 根据 action 更新 store,然后响应到 view 层。
在这里插入图片描述

useState vs useReducer

state 更新方面,使用 useReducer 和使用 useStatesetState 传入回调函数时的效果一样,即 state 的更新是异步的,并且不会合并。

🌰
useState

function Counter() {
  const [count, setCount] = useState(0);
  console.log("render");
  
  return (
    <div>
      count: {count}
      <button
        onClick={() => {
          setCount((c) => c + 1);
          console.log("count", count);
          setCount((c) => c + 1);
          console.log("count", count);
        }}
      >
        +
      </button>
    </div>
  );
}

useReducer

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  console.log("render");
  
  return (
    <div>
      count: {state.count}
      <button
        onClick={() => {
          dispatch({ type: "increment" });
          console.log("count", state.count);
          dispatch({ type: "increment" });
          console.log("count", state.count);
        }}
      >
        +
      </button>
    </div>
  );
}

点击 + 后,都是这个结果 ↓
在这里插入图片描述
useState 是细粒度的状态管理。本质来说,useState 就是预置了 reduceruseReducer,即 每次调用 setState,相当于调用 dispatchreducer 把传入值赋值到对应的 state

reducer 体现的编程思想,类似于设计模式中的状态模式。状态模式是,当不同的状态有不同的行为,并且在代码中存在大量和状态相关的条件语句的时候,把具体的状态类抽象出来,最后只用委托状态类调用其中的方法即可,不用关心具体的实现。reducer 是,每一种状态对应一组数据,状态变化的时候就更新对应组的数据,使用时也不用关心具体数据。当一种状态对应多个数据变化,比如一个操作需要更新多个 state 的时候,reducer 就能聚合了这种变化。

useReducer 的使用场景

当你想更新一个状态,并且这个状态更新依赖于另一个状态的值时,你可能需要用 useReducer 去替换它们。

场景一:需要同时更新多个状态时

🌰
使用 useState 时:

function LoginPage() {
  const [name, setName] = useState(""); // 用户名
  const [pwd, setPwd] = useState(""); // 密码
  const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
  const [error, setError] = useState(""); // 错误信息
  const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录

  const login = (event) => {
    event.preventDefault();
    setError("");
    setIsLoading(true);
    login({ name, pwd })
      .then(() => {
        setIsLoggedIn(true);
        setIsLoading(false);
      })
      .catch((error) => {
        // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
        setError(error.message);
        setName("");
        setPwd("");
        setIsLoading(false);
      });
  };
  return (
	// 返回页面 JSX Element
  );
}

使用 useReducer 时:

const initState = {
  name: "",
  pwd: "",
  isLoading: false,
  error: "",
  isLoggedIn: false,
};
function loginReducer(state, action) {
  switch (action.type) {
    case "login":
      return {
        ...state,
        isLoading: true,
        error: "",
      };
    case "success":
      return {
        ...state,
        isLoggedIn: true,
        isLoading: false,
      };
    case "error":
      return {
        ...state,
        error: action.payload.error,
        name: "",
        pwd: "",
        isLoading: false,
      };
    default:
      return state;
  }
}
function LoginPage() {
  const [state, dispatch] = useReducer(loginReducer, initState);
  const { name, pwd, isLoading, error, isLoggedIn } = state;
  const login = (event) => {
    event.preventDefault();
    dispatch({ type: "login" });
    login({ name, pwd })
      .then(() => {
        dispatch({ type: "success" });
      })
      .catch((error) => {
        dispatch({
          type: "error",
          payload: { error: error.message },
        });
      });
  };
  return (
    // 返回页面 JSX Element
  )
}

场景二:解耦,移除不必需的依赖,避免不必要的 effect 调用

🌰
使用 useState 时:

function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount((c) => c + step);
    }, 1000);
    return () => clearInterval(id);
  }, [step]);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={(e) => setStep(Number(e.target.value))} />
    </>
  );
}

使用 useReducer 时:

const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === "tick") {
    return { count: count + step, step };
  } else if (action.type === "step") {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: "tick" });
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={(e) => setStep(Number(e.target.value))} />
    </>
  );
}

使用场景总结

可以在一个自定义 hook 或组件中同时使用多个 useStateuseReducer

按照使用作用域分离 state,如果它们一起更新,最好将其放在 reducer 中;如果某项 state 与该自定义 hook 或组件中的其他 state 来说非常独立,那么将其与其他 state 绑定在一起只会给 reducer 增加不必要的复杂性,最好就让它保持 useState 的形式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值