Redux Toolkit工具使用

Redux Toolkit是Redux官方推出的工具集,因为Redux编写过程中会写很多的样板代码,对开发者不太友好,因此官方推出Redux Toolkit,Redux Toolkit就是对Redux的二次封装,用于高效 Redux 开发,使 Redux 的使用变得简单,下面用 Redux Toolkit 实现 TodoList 案例

npm i @reduxjs/toolkit

目录结构
在这里插入图片描述

创建状态切片

对于状态切片,我们可以理解为就是 Reducer 和 Action 的集合体;在 Redux 中,原本 Reducer 函数和 Action 对象需要分别创建,现在通过状态切片替代,它会返回 Reducer 函数和 Action 对象

通过引入 createSlice 创建切片,给 createSlice 调用返回的 reducer 起一个别名 TodosReducer ,避免跟其他的 reducer 冲突

import { createSlice } from "@reduxjs/toolkit"
const { reducer: TodosReducer, actions } = createSlice()

配置状态切片

createSlice 定义 name 使用常量,后续 reducer 其他地方夜也会使用到常量,如需更改,只需更改常量的值,导出常量;initialState 初始状态值;reducers 就是一个个小的reducer集合,导出 reducer 跟 action
详细配置参考链接: https://redux-toolkit.js.org/api/createSlice.

export const TODOS_FEATURE_KEY = "todos"
const { reducer: TodosReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
		state.push(action.payload)
	}
  }
})
export const { addTodo } = actions
export default TodosReducer

创建 store

在 index.js 文件中创建 store, 使用 configureStore 配置 store,配置项中的reducer 对象的作用就是合并 reducer ,对应 redux 中的 combineReducers;devTools 配置为布尔值,这里非生产环境使用,

import { configureStore} from "@reduxjs/toolkit"
import TodosReducer, { TODOS_FEATURE_KEY } from "./todos.slice"

export default configureStore({
  reducer: { // combineReducers
    [TODOS_FEATURE_KEY]: TodosReducer
  },
  devTools: process.env.NODE_ENV !== "production"
})

Action 预处理

Action 被触发后, 可以通过 prepare 方法对 Action 进行预处理,就是在触发 reducer 之前去处理 Action ,Reducer. prepare 方法必须返回对象,原来的 createSlice 的配置 reducers 对象中的 reducer 函数也要配置为对象

addTodo: {
    reducer: (state, action) => {
    	console.log(action)
		state.push(action.payload)
	},
    prepare: todo => {
      return {
        payload: { cid: Math.random(), ...todo }
      }
    }
}

配置中间件

在 configureStore 配置 middleware ,首先需要配置默认中间件,再配置自己的中间件
store 中的完整代码

import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"
import TodosReducer, { TODOS_FEATURE_KEY } from "./todos.slice"
import logger from "redux-logger"

export default configureStore({
  reducer: {
    [TODOS_FEATURE_KEY]: TodosReducer
  },
  devTools: process.env.NODE_ENV !== "production",
  middleware: [...getDefaultMiddleware(), logger]
})

Redux Toolkit异步操作

Redux Toolkit异步操作需要用到Redux Toolkit工具集提供的方法createAsyncThunk,方法类似于 redux 中间件方法,用来创建执行异步操作的 actioncreator 函数;这个方法的第一个参数是 action 对象的 type 属性,是一个字符串;第二个参数是一个函数,函数作用执行异步操作,函数有两个参数,第一个参数是 payload ,就是在组件调用通过 createAsyncThunk 方法创建出来的 actioncreator 函数时传入的参数,第二个参数是 thunkAPI ,可以通过 thunkAPI 对象拿到 dispatch 方法,这样就可以通过 dispatch 触发另一个 action 保存得到的数据

export const loadTodos = createAsyncThunk("todos/loadTodos", payload =>
  axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
)

在 createSlice 配置项 reducers 中创建 setTodos 函数,在 actions 中再次解构导出 setTodos

reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { cid: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      action.payload.forEach(todo => state.push(todo))
    }
}
export const { addTodo, setTodos } = actions

改进Redux Toolkit异步操作

在 createAsyncThunk 的时候,通过 thunkAPI 提供的 dispatch 触发另外一个action 保存状态,这样太麻烦了,在创建切片的时候(createSlice),可以传入配置 extraReducers ,extraReducers 也是配置 reducer 的,reducers 配置的是同步的 reducer ,extraReducers 配置的是异步的,loadTodos 返回一个Promise对象, extraReducers 中还可以监听接口请求的状态

export const loadTodos = createAsyncThunk("todos/loadTodos", payload =>
  axios.get(payload).then(response => response.data)
)
extraReducers: {
    [loadTodos.pending]: (state, action) => {
      console.log("pending")
      return state
    },
    [loadTodos.fulfilled]: (state, action) => {
		action.payload.forEach(todo => state.push(todo))
	}
  }

实体适配器

实体适配器可以理解为一个容器,放入状态的容器,将状态放入实体适配器,实体适配器提供操作状态的各种方法,简化操作;通过 createEntityAdapter 方法调用得到容器对象 todosAdapter ;todosAdapter 中的 getInitialState 有两个属性,entities 和 ids,entities 存储的是一条一条的状态,ids 是对应的状态的 id,getInitialState 可以做为初始状态值;todosAdapter 还有 addOne 和 addMany 方法,方法就是往对象中添加一条和多条数据,addMany 会默认循环 action 中的数据

const todosAdapter = createEntityAdapter()
const { reducer: TodosReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer:  (state, action) => {
    	console.log(action)
    	//state.push(action.payload)
		todosAdapter.addOne(state, action.payload)
	  },
      prepare: todo => {
        return {
          payload: { cid: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      //action.payload.forEach(todo => state.push(todo))
      todosAdapter.addMany(state, action.payload)
    }
  },
  extraReducers: {
    [loadTodos.pending]: (state, action) => {
      console.log("pending")
      return state
    },
    [loadTodos.fulfilled]: (state, action) => {
      //action.payload.forEach(todo => state.push(todo))
      todosAdapter.addMany(state, action.payload)
    }
  }
})

addOne 和 addMany 方法会检测第二个参数是不是 action ,如果是 action 会默认使用 payload 属性,因此 reducers 和 extraReducers 配置代码简化

reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { cid: Math.random(), ...todo }
        }
      }
    },
    setTodos: todosAdapter.addMany
},
extraReducers: {
    [loadTodos.pending]: (state, action) => {
      console.log("pending")
      return state
    },
    [loadTodos.fulfilled]: todosAdapter.addMany
}

实体适配器要求每一个实体必须拥有 id 属性作为唯一标识,如果实体中的唯一标识字段不叫做 id,需要使用selectId 进行声明
配置链接: https://redux-toolkit.js.org/api/createEntityAdapter.

const todosAdapter = createEntityAdapter({
  selectId: todo => todo.cid
})

状态选择器

提供从实体适配器中获取状态的快捷途径。需要用到 createSelector,使用链接: https://redux-toolkit.js.org/api/createSelector

import {
  createSlice,
  createAsyncThunk,
  createEntityAdapter,
  createSelector
} from "@reduxjs/toolkit"

const { selectAll } = todosAdapter.getSelectors()

export const selectTodos = createSelector(
  state => state[TODOS_FEATURE_KEY],
  selectAll
)

组件使用

import { useDispatch, useSelector } from "react-redux"
import { addTodo, loadTodos, selectTodos } from "../../Store/todos.slice"
import { useEffect } from "react"

function Main() {
  const dispatch = useDispatch()
  const todos = useSelector(selectTodos)
  useEffect(() => {
    dispatch(loadTodos("http://localhost:3001/todos"))
  }, [])
  return (
    <section className="main">
      <button onClick={() => dispatch(addTodo({ title: "测试任务" }))}>
        添加任务
      </button>
      <ul className="todo-list">
        {todos.map(todo => (
          <li key={todo.cid}>
            <div className="view">
              <input className="toggle" type="checkbox" />
              <label>{todo.title}</label>
              <button className="destroy" />
            </div>
            <input className="edit" />
          </li>
        ))}
      </ul>
    </section>
  )
}

export default Main

版权声明:本文为weixin_43016472原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。