前端之状态管理

60 阅读2分钟

状态管理

组件嵌套层级很多,props层层传递不合适,使用状态管理进行集中、统一管理页面数据

状态提升

Context 跨层级传递

切换主题、语言

useContext()
export const ThemeContext=createContext(themes.light)
<ThemeContext.Provider value={themes.light}>
const theme=useContext(ThemeContext)

useReducer

简化的 redux,复杂数据结构

根据传入的 action 返回新的 state

function reducer(state:StateType,action:ActionType){
  switch(action.type){
    case 'add':
      return { count: state.count + 1 }
    default:
      throw new Error()
  }
}

// use
const [state, dispatch]=useReducer(reducer,initialState) 
return (
  <button onClick={()=> dispatch({ type:'add' })} />
)

state action reducer dispatch

使用 Context + useReducer(state ,dispatch)实现 todoList 组件开发

不能模块化

Redux

功能完善,可拆分模块

// ----------   count.ts   ----------

import {createSlice} from '@reduxjss/toolkit'
const INIT_STATE: number = 100
export const countSlice=createSlice({
  name: 'count',
  initialState: INIT_STATE,
  reducers: {
    add(state: number){
       return state + 1
    },
    lower(state: number){
       return state - 1
    }
  }
})

export const {add,lower} = countSlice.actions
export default countSlice.reducer

// ----------   index.ts   ----------

export type StateType = {
  count: number
}
export default configureStore({
  reducer: {
    count: countReducer
    // 其他模块
  }
})


// ----------   use   ----------

const count= useSelector<StateType>(state =>state.count)
const dispatch = useDispatch()

<button onClick={()=>dispatch(add())} />+

使用 redux 重新实现 todoList nanoid 随机数

单向数据流

<Provider> store reducer action dispatch 配合immer

MobX 声明式的修改数据

state action derivation computed observable

class Timer {
  time=''
  reset(){ }
  constructor() {
    makeAutoObservable(this,{
      time: observable,
      reset: action
    })
  }
}

store.ts 定义 ObservableTodoStore 和 ObservableTodoListStore class,在constructor()中定义makeObservable(this,{})

index.tsx 页面主组件

TodoList.tsx 渲染出列表

TodoView.tsx 显示todo

管理用户信息

src/store/userReducer.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export type UserStateType = {
  username: string;
  nickname: string;
};

const INIT_STATE: UserStateType = { username: "", nickname: "" };

export const userSlice = createSlice({
  name: "user",
  initialState: INIT_STATE,
  reducers: {
    loginReducer: (
      state: UserStateType,
      action: PayloadAction<UserStateType>
    ) => {
      return action.payload; // 设置 username、nickname 到 redux store
    },
    logoutReducer: () => INIT_STATE, // 重置 state
  },
});

export const { loginReducer, logoutReducer } = userSlice.actions;

export default userSlice.reducer;

src/store/index.ts

import { configureStore } from '@reduxjs/toolkit';
import userReducer,{ UserStateType } from './userReducer'

export type StateType={
    user:UserStateType
}

export default configureStore({
    reducer:{
        // user
        user: userReducer
    }
})

main.tsx

<Provider store={store}>
      <Edit/>
    </Provider>

使用: 三个 hook useGetUserInfo

useLoadUserData

import { useState, useEffect } from "react";
import { useRequest } from 'ahooks'
import { useDispatch } from "react-redux";
// 
import useGetUserInfo from "./useGetUserInfo";
import { getUserInfoService } from '@/services/user'
import { loginReducer } from '@/store/userReducer'


function useLoadUserData() {
  // ajax请求用户数据后,直接放在 redux中
  // 这里不需要再返回数据了
  const [waitingUserData, setWaitingUserData] = useState(true);

  const dispatch=useDispatch()
  // ajax 
  const { run }=useRequest(getUserInfoService,{
    manual: true,
    onSuccess(res){
        const {username,nickname}=res
        dispatch(loginReducer({username,nickname}))
    },
    onFinally(){
        setWaitingUserData(false)
    }
  })

  const { username } =useGetUserInfo()
  useEffect(()=>{
    if(username){
        setWaitingUserData(false)
        return
    }
    run()
  },[username])

  return { waitingUserData };
}

export default useLoadUserData;

useNavPage

import { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
//
import useGetUserInfo from './useGetUserInfo'
import { ROUTE_PATH, isLoginOrRegister, isNoNeedUserInfo } from '@/router'

function useNavPage(waitingUserData:boolean){
    const { username }=useGetUserInfo()
    const { pathname}=useLocation()
    const nav =useNavigate()

    useEffect(()=>{
        if(waitingUserData)
            return
        // already login
        if(username){
            if(isLoginOrRegister(pathname)){
                nav(ROUTE_PATH.QUESTION_EDIT)
            }
            return
        }
        // not login
        if(isNoNeedUserInfo(pathname)){
            return
        }else{
            nav(ROUTE_PATH.LOGIN)
        }
    },[waitingUserData,username,pathname])
}

export default useNavPage