Redux--温故而知新

114 阅读4分钟

不可变性

Redux状态的更新都是不可变的

如果想要不可变的方式来更新,那么代码就必须先复制原来的array/object,然后在进行更新,展开符(...)就可以起到这个作用

const obj1 = {
    a:{
        name:summer
    },
    b:2
}
const obj2  = {
    ...obj1,
    b:22
}
const arr1 = [1,2,3];
//创建arr1的备份并将5放在最后
const arr2 = arr1.concat(5)

Action

Action是一个包含了type,payload的对象

  • type:是指action的类型
  • payload:是指action携带的数据

action需要通过dispatch来发送

一般情况下,action不会直接写为一个对象,而是需要一个函数,它返回的是一个action(Action Creator)

const addTodo = (text)=>{
    return {
        type:'ADD_TODO',
        payload:text
    }
}

所以现在如果想触发这个动作,只需要dispatch(addTodo(text))

Reducer

reducer是一个接受state和action的函数

reducer禁止直接修改stateredux要求一切都是不可变的,必须通过复制然后进行修改

因为type可能有很多,所以我们一般使用if/else,switch来判断更新哪个状态

const initialState = {
  a: 'a',
  b: 'b'
};

function someApp(state = initialState, action) {
  switch (action.type) {
    case 'CHANGE_A':
      return { ...state, a: 'Modified a' };
    case 'CHANGE_B':
      return { ...state, b: action.payload };
    default:
      return state
  }
}

reducer里面不可以进行

  • 直接操作state
  • 请求api
  • 调用不纯的函数:Math.random(),Data.now()

什么是纯函数

  • 给相同的参数,返回的一直是相同的输出
  • 没有任何副作用(发送网络请求等)’

一般我们会将Reducer拆开,这时候合并的时候我们就需要combineReducers

import {combineReducers} from 'redux'
const rootReducer = combineReducers({
    a:ReducerA,
    b:ReducerB
})

Store

store就是用来连接actionreducer都东西

作用:

  • 提供getState()方法来获取State

  • 提供dispatch方法来发送action更改State

    dispatch(addTodo(text))

  • 提供subscribe函数,注册回调函数监听state的更改

创建一个Store很容易,只需要将刚刚的RootReduer传递过去

import { configureStore } from '@reduxjs/toolkit'

const store  = configureStore({
    reducer:rootReducer
})

dispatch

他是更改state的唯一方法:调用store.dispatch()并传入一个action对象

//action
const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

RTK新增

切片

"切片"是应用中单个功能的Redux reducer逻辑和action的集合

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'

export default configureStore({
  reducer: {
    users: usersReducer,
    posts: postsReducer,
    comments: commentsReducer
  }
})

configureStore

这个取代了之前的createStore

之前使用createStore

import { createStore } from 'redux';
import rootReducer from './reducers';
let store = createStore(rootReducer);

现在可以是

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'

// 这个store已经集成了redux-thunk和Redux DevTools
const store = configureStore({ reducer: rootReducer })

createActionCreateReducer

之前Action

const INCREMENT = 'counter/increment'
function increment(amount:number){
	return {
        type:INCREMENT,
        payload:amount
    }
}
const action = increment(3)

const initialState = {
  a: 'a',
  b: 'b'
};
function someApp(state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, a: 'Modified a' };
    default:
      return state
  }

使用createActioncreateReducer

内部集成了immer,它可以保证不变性

并且不再需要这种switch

import { createAction, createReducer } from '@reduxjs/toolkit'
//创建接口
interface CounterState{
	value:number
}
//创建Action
//就省去了Action Create这一步,因为内部已经做了
const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction<number>('counter/incrementByAmount')

//reducer需要两参数,一个是state,一个是action
const initState:countState = {value:0}
//创建Reducer
const countReducer = createReducer(initState,(builder)=>{
    builder
    	.addCase(increment,(state,action)=>{
        	state.value++
    	})
    	.addCase(decrement,(state,action)=>{
        	state.value--
    	})
    	.addCase(incrementByAmount,(state,action)=>{
        	state.value+=action.payload
    	})
})

build提供了3中方法

  • addCase:根据Action提供一个Reducer case操作
  • addMatcher:可以进行过滤
  • addDefaultCase:默认值,等价于switch

CreateSlice

createSlice相当于将ActionReducer结合起来

接收initial state、reducer、action creator和action types

您所要做的就是为这个切片定义一个名称编写一个包含 reducer 函数的对象,它会自动生成相应的 action 代码

//设置iniState类型
export interface InitStateProps{
    loading:boolean;
    visible:boolean;
    isEditMode:ooolean;
    fromValue:CustomerTypes;
    customerList:CustomerTypes[];
    fetchParams:ParamsTypes;
}
//设置initState
const initState:InitStateProps{
    loading:false;
    visible:false;
    isEditMode:false;
    fromValue:{};
    customerList:[];
    fetchParams:{};
}

//创建一个slice
const customerSlice = createSlice({
    name:customer,//命名空间
    initState,
    reducers:{
        changeLoading: (state: InitialStateTypes, action: PayloadAction<boolean>) => {
      		state.loading = action.payload;
    	},
    	changeCustomerModel: (state: InitialStateTypes, action: PayloadAction<IndexProps>) => {
          const { isOpen, value } = action.payload;
          state.visible = isOpen;
          if (value) {
            state.isEditMode = true;
            state.formValue = value;
          } else {
            state.isEditMode = false;
          }
    	}
    }
})

在React中使用

import React,{useState} from 'react'
import {useSelector,useDispatch} from 'react-redux'
import {
  changeCustomerModel,
   changeLoading 
} from '@root/store/reducer/customer';
export default()=>{
    const dispatch = useDispatch();
    //取值
    const {loading,visible,isEditMode,formValue,customerList,fetchParams} = useSelector(
        //state.name
    (state:ReducerTypes)=> state.customer
    )
    
    return (
    <div>....</div>
    )
}

reducer里面的action,其实就是你当时调用的时候传进去的参数

PrepareReducer

一般情况下,我们直接使用createSlice,里面加一个含有reducer的函数,就可以了

但是我们有时候需要从不同的组件dispatch不同的组件,这时候我们就需要用到prepare

const postSlice = createSlice({
    name: 'posts',
    initialState: initState,
    reducers: {
        // postAdd(state, action) {
        //     console.log("state",state);
        //     console.log("action",action);
        //     state.push(action.payload)
        // },
        postAdd: {
            reducer(state, action) {
                console.log("reducer-action",action);
                console.log("reducer-state",state);
                state.push(action.payload)
            },
            prepare({ title, content, userId }) {
                console.log("prepare",title, content, userId);
                return {
                    payload: {
                        id: nanoid(),
                        title,
                        content,
                        user: userId
                    }
                }
            }
        },
        postUpdate(state, action) {
            const { id, title, content } = action.payload
            const existingPost = state.find(post => post.id === id)
            existingPost.title = title
            existingPost.content = content
        }
    }
})

prepare会接受更多的参数,里面也可以写不纯的函数,他会返回一个payload对象,最后createslice会将return出来的payLoad作为新的action,再到reducer·里面使用

useSelector

可以从ReduxStore状态中提取他需要的任何数据

.....
const {loading,visible,isEditMode,formValue,customerList,fetchParams} = useSelector(
        //state.name
    (state:ReducerTypes)=> state.customer
    )

获取state.name,就是当时你createSlice设置的name

参考链接:

Redux 中文官网

Redux 基础 | React 入门教程 (gitbooks.io)

@reduxjs/toolkit黑魔法 - 掘金 (juejin.cn)