七、学习useReducer

一、什么是useReducer?

​ useReducer是react hooks中提供的一个高级hooks,即便没有这些高级hooks,只用useState、useEffect和useContext,也能完成需求。但是这些高级的hooks可以让我们的代码具有更好的可读性、可维护性、可预测性。

二、useReducer的出现是为了解决什么问题?

要理解这个问题,我们得先知道reducer是什么东西。reducer是redux中一个概念,以下就是redux更新数据的一个流程。

七、学习useReducer_第1张图片

Store, Reducer, Action是Redux的三大核心概念,同时也是useReducer的三大核心概念。redux是当前react项目中状态管理的一个主流方案,但是redux因为复杂的概念以及较高的维护成本,它的开发体验一直是一个难以解决的痛点。

1、难以维护的Action

2、复杂度无法预知的Store

3、复杂到令人绝望的Reducer

​ 但useReducer也并没有提供相应的解决方案,useReducer只是一个小范围内的状态管理工具,它是用来在某些特殊场景下,来取代useState的,它把redux的复杂度控制在了可以接受的范围之内。

​ 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

三、useReducer的基本使用

//正常使用
const [state,dispatch]=useReducer(reducer,initState);
//惰性初始化
const [state, dispatch] = useReducer(reducer, initialArg, init);

​ 它接收一个形如 (state, action) => newState 的 reducer和一个初始值。当然这个初始值也可以通过一个函数计算出来,也就是惰性初始化。如果做惰性初始化,那么useReducer就传递了三个参数,第一个参数依旧是reducer,第三个参数是一个初始化函数,第二个参数是初始化函数要传递的参数列表。

​ 尽管 useReducer 是扩展的 hook, 而 useState 是基本的 hook,但 useState 实际上执行的也是一个 useReducer。这意味着 useReducer 是更原生的,你能在任何使用 useState 的地方都替换成使用 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);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

惰性初始化:

function init(initialCount) {  return {count: initialCount};}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':      return init(action.payload);    default:
      throw new Error();
  }
}
 
function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

一个登录的例子,假设没有useReducer

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
        )
    }

虽然代码似乎变多了,但是比起使用useState,代码更容易读懂,更容易维护了。

四、更复杂的例子,与useRef配合

​ 再来看看更接近典型Redux reducer 的例子。创建一个组件来管理购物列表,这里看还会使用另外一个 hook:useRef

​ 首先,导入两个hook

import React, { useReducer, useRef } from 'react';

然后创建一个ref和reducer的组件。ref保存对表单的引用,以便咱们提取这个表单的值。

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      // do something with the action
    }
  }, []);

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
          </li>
        ))}
      </ul>
    </>
  );
}

请注意,在这种情况下,咱们的“state”是一个数组。 咱们通过useReducer第二个参数将它初始化为一个空数组,并从reducer函数返回一个数组。

扩展:useRef hook

useRef hook为DOM节点创建持久引用。 调用useRef会创建一个空的节点。它返回的对象具有current属性,因此在上面的示例中,咱们可以使用inputRef.current访问输入的DOM节点。 如果你熟悉React.createRef(),则其工作原理非常相似。

​ 但是,useRef返回的对象不仅仅是一种保存DOM引用的方法。 它可以保存特定于此组件实例的任何值,并且它在渲染之间保持不变。

useRef可用于创建通用实例变量,就像使用具有this.whatever = valueReact类组件一样。 唯一的问题是,写入它会被视为“副作用”,因此咱们无法在渲染过程中更改它,需要在useEffect hook 中才能修改。

回到useReducer示例:

​ 我们用表单来处理用户的输入,按回车提交表单。 现在来编写handleSubmit函数,该函数主要做的是将一个项添加到列表中,以及处理reducer中的 action

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        return [
          ...state,
          {
            id: state.length,
            name: action.name
          }
        ];
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: 'add',
      name: inputRef.current.value
    });
    inputRef.current.value = '';
  }

  return (
    // ... same ...
  );
}

reducer函数中主要判断两种情况:一种用于action.type==='add'的情况,还有就是默认下的情况。

action.typeadd 时,它返回一个包含所有旧元素的新数组,以及最后的新元素。

这里有一点需要注意的是,咱们使用数组的length作为一种自动递增的 ID 方便演示,但是对于一个真正的应用程序来说这是不可靠,因为它可能导致重复的ID和bug。(最好使用uuid这样的库,或者让服务器生成一个惟一的ID!)

当用户在输入框中按Enter键时会调用handleSubmit函数,因此咱们需要调用preventDefault以避免在发生这种情况时重新加载整页。 然后dispatch派发一个 action

删除项

现在来看看如何从列表中删除项的。

在项目中添加一个删除

你可能感兴趣的:(React,javascript,reactjs,前端)