redux使用

Flux概念,将一个应用分成四个部分

1.View,视图层
2.Action,视图层发出的消息,动作
3.Dispatcher,用来接收Actions、执行回调函数
4.Store,用来存放应用的状态,一旦发生变动,提醒views更新页面
Flux的特点就是,单向数据流
1.用户访问View
2. View发出用户的Action
3. Dispatcher收到Action,要求Store进行相应的更新
4. Store 更新后,发出一个"change"事件
5. View收到"change’事件后,更新页面
请添加图片描述

Redux,

  • js应用的状态容器,不仅仅用于react,vue也能用,只是现在多用于react,因为React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。没有官方标配的状态管理器
  • 浏览器安装Redux Devtools插件,让你在浏览器中能看到redux仓库的数据状态
  • 使用这个插件要在createStore中配置window.REDUX_DEVTOOLS_EXTENSION && window.REDUX_DEVTOOLS_EXTENSION()
    或者
    安装@redux-devtools/extension
    在createStore中,最新的redux推荐使用@reduxjs/toolkit称作RTK
    基本结构:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';

function reducer(state,action){
	switch(action.type){
		default:
			return state
	}
}
// 无需中间件时
const store = createStore(
  reducer,
  composeWithDevTools()
);

// 需要中间件时这样写
const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(...middleware)
  )
);

安装Redux:npm install redux

  • 创建一个reducer纯函数,用于createStore,这个函数与useReducer hook接收的reducer函数一样,会接收到上一次的state与一个action动作对象
    1.可以给state参数设置默认值,来描述仓库state数据的初始值
    2.action是初始化动作,由Redux生成,里面的type字段的值是一个随机数,通过dispatch来派发一个action对象来规定type值
function reducer(state,action){
	console.log(state,action)
	//通过不同的type返回对state做不同的处理并返回
	//注意不能直接对state做修改返回,而是生出一个全新的state来返回
	switch(action.type){
		default:
			return state
	}
}

1.该函数在创建仓库时,默认会触发一次
2.该函数在后续调用仓库的dispatch方法时,会再次触发
3.该函数需要根据state与action生成一份全新的state数据,并返回
4.该函数返回的数据,就是state。也就是仓库的getState()返回的值

const store = createStore(reducer)

  • store是一个仓库对象,他有以下常用方法
    • getState() 返回当前仓库中的state数据
    • dispatch(action) 派发一个动作,用于修改仓库中的state
    • subscribe(callback) 用于订阅仓库的数据变化,当仓库数据发生变化时会触发calback

react-redux

  • react-redux 是用于更好的在 react 中链接 redux 使用的。主要使用手法有两种:
    Provider + 高阶组件的方式
    Provider + hooks的方式
  • Provider 是 react-redux 提供个一个组件。将该组件用于项目根,用该组件将根组件包裹,且将 store 仓库对象作为数据传递给 Provider
  • Provider+高阶组件的方式:
    • 语法:const NewComponent = connect(mapStateToProps,mapDispatchToProps)(WrappedComponent)
      1.mapStateToProps 映射仓库state到WrappedComponent的Props上的一个函数
      2.mapDispatchToProps 映射仓库修改state的操作到WrappedComponent的Props上的一个函数
      3.WrappedComponent 需要被包装的组件
      4.NewComponent 返回的新组件
    • mapStateToProps()接收两个参数,
      1.state仓库的state数据,
      2.props当前组件收到的props数据
      这个函数return的数据会合并到组件的props中去
      该函数默认会触发一次,当仓库数据发生变化,这个函数会再次触发,就无需subscribe来监听数据变化
    • mapDispatchToProps()
      必须返回一个对象,里面一般存放来修改数据的函数,这个对象也会合并到组件的props中去
  • 上面的方式看起来非常的复杂,所以不做详细描述了,用下面这个方式更方便
  • Provider+hooks的方式:
    用Provider包裹你的根组件,在main.jsx文件中,引入创建的仓库store
import React from "react";
import ReactDOM from "react-dom/client";
// 引入 react-redux 的 Provider
import { Provider } from "react-redux";

// 引入 store
import store from "./store";

import App from "./App";

// 创建容器
const root = ReactDOM.createRoot(document.querySelector("#root") as Element);

root.render(
//将App组件包裹,store就是绑定你创建的的仓库对象
  <Provider store={store}>
    <App />
  </Provider>
);

在子组件中,引入下面两个方法:import { useSelector, useDispatch } from ‘react-redux’

  • useSelector(),用于勾出仓库的某数据
    • const age = useSelector(state=>state.age) 这里假设勾出state数据中有一个age属性
  • useDispatch(),用于派发一个action动作对数据做操作
    • const dispatch = useDispatch()
    • 在事件中,dispatch({type:‘add’})派发一个add动作,随即就会在renducer中执行对应的动作

reducer的拆分

  • 当一个项目越做越大,状态越来越多,renducer中的case越来越多在同一个文件中,不利于维护,所以需要对其进行拆分
  • 如何拆:
    store下创建一个reducers文件夹来存放拆分的case项
//这里的initState只属于这个arrReducer小模块,并不是仓库最终的state
const initState = [{uname:'张三',age:18}]
//导出这个arrReducer函数
export default function arrReducer(state = initState,acation){
	switch(action.type){
		case "arr/push":
			return [...state,action.payload]
		case "arr/unshift":
			return [action.payload,...state]
		default:
			return state
	}
}
  • 如何合并:
    • 使用redux的combineReducers方法
    • 语法:const rootReducer = combineReducers({key1:reducer1,key2:reducer2})
      key:自定义的键名,最终的state的key
      reducer:拆分出去的后,引入进来的名字
//导入
import { combineReducers ,createStore} from 'redux'
import { composeWithDevTools } from '@redux-devtools/extension';

//引入拆分出去的reducer文件
import arrReducer from './arrReducer'

//调用combineReducers做合并,生成合并后的rootReducer
const rootReducer = combineReducers({
	arr:arrReducer
})

const store = createStore(rootReducer,composeWithDevTools())
export default store
  • 在之后组件中,通过前面的Provider+hook的方式
    useSelector(state=>state.arr)通过state.之前合并时的key就能拿到拆分出去的对应的数据
    useDispatch()({type:‘arr/push’})这里的dispatch照常使用就行,注意type的值要对应,格式大小写不能错

中间件

  • 用于对action动作执行之前做一些处理
  • 在store下新建middlewares文件夹,用于放中间件函数文件
  • 例如:创建一个a.ts文件
    在a.ts文件中定义中间件函数:
    export default (storeAPI) => (next) => (action)=> {
    next(action)
    }
    在创建store的createStore中
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';

//引入中间件函数
import aMidd from './middlewares/a'
import bMidd from './middlewares/b'
// 需要中间件时
const store = createStore(
  reducer,
  composeWithDevTools(
  	//在上一个aMidd的中间件函数中next()了,才会继续走下面的bMidd中间件
	applyMiddleware(aMidd,bMidd)
  )
);

redux-logger中间件

通过npm安装:npm i redux-logger
引入之后直接放到applyMiddleware中使用:import logger from ‘redux-logger’
让你打印仓库日志消息
这个中间件必须放在中间件流程applyMiddleware的最后一个

redux-thunk中间件

通过npm安装:npm i redux-thunk
引入之后直接放到applyMiddleware中使用:import thunk from ‘redux-thunk’
让你的dispatch可以传函数来执行异步操作

immer库中的produce使用

下载依赖:npm i immer

//引入immer的produce
import { produce } from 'immer'
//定义一个原数据
const oldState = [{uname:'张三',age:18},{uname:'李四',age:20}]
//调用produce方法,对数据做一些操作
const newState = produce(oldState,(draft)=>{
	draft[1].age = 18
	draft.push({uname:'王五',age:20})
})

通过这样的方式,原数据不会被修改,返回一个新的数据,避免了原生js写法要拷贝数据等一些繁琐的操作

把immer用于redux中

  • 方式一
import { createStore } from "redux";
import { composeWithDevTools } from "@redux-devtools/extension";
import { produce } from "immer";

//定义初始的数据
const initialState = {
  count: 0,
};

function rootReducer(state = initialState, action: any) {
  switch (action.type) {
    case 'inc':
	//利用这种方式,返回的是一个在原数据的基础上修改后的新数据,当数据的层级多时,
	//不需要拷贝,展开等操作,直接按最简便的方式对newState进行修改就行
      return produce(state, (newState) => {
        newState.count++;
      });
    case 'dnc':
      return produce(state, (newState) => {
        newState.count--;
      });
    case 'incbynum':
      return produce(state, (newState) => {
        newState.count += action.payload;
      });
    default:
      return state;
  }
}

export default createStore(
  rootReducer,
  composeWithDevTools()
);
  • 方式二,进一步简化
    把rootReducer换一个方式来描述
const rootReducer = produce((state = initialState, action: any) {
  switch (action.type) {
    case 'inc':
      state.count++;
	//在produce中的return默认就是返回新数据,当然也可以写return state,不过return不能省略
      return
    case 'dnc':
      state.count--;
      return
    case 'incbynum':
      state.count += action.payload;
      return
    default:
      return state;
  }
})

@reduxjs/toolkit的使用

通过这个方法可以极大的省略前面所写的一系列繁琐操作
@reduxjs/toolkit中已经包含了redux的核心,redux-thunk,@reduxjs/devetools,immer库等等

  • 安装:npm i @reduxjs/toolkit,同时别忘了使用npm i react-redux
  • 从创建主仓库开始:
// 引入 配置仓库的 函数
import { configureStore } from "@reduxjs/toolkit";

// 这里引入的是切片文件, 也就是相当于前面把reducer拆分出去的文件,默认引入的就是一个 reducer 函数
import countReducer from "./stores/count";

// 调用 configureStore, 并配置好 reducer 选项,返回的就是一个 store 对象
const store = configureStore({
  // 这个 reducer 配置,类似于 combineReducers 的写法
  reducer: {
    // 把引入的countReducer配置到这里
    count: countReducer,
  },
});

// 导出 store
export default store;
  • 接着创建切片文件
    这里在store的stores下创建一个count.js作为示例
//引入createSlice方法
import { createSlice } from "@reduxjs/toolkit";
//调用createSlice方法编写切片的配置会返回一个切片给countSlice接收
const countSlice = createSlice({
  // 切片的名字,全局唯一。而且该 name 会作为 actionType 的前缀。
  name: "count",
  // initialState来初始化的state数据
  initialState: {
    num: 0,
  },
  //配置reducer,每一个方法就像是前面配置reducer中的case,而这些方法在组件中通过countSlice.actions.xxx来使用
  reducers: {
    inc(state, action) {
      // 由于整个RTK集成了immer.所以你可以直接操作state来修改
      state.num++;
    },
    dec(state) {
      state.num--;
    },
  },
});
// 切片对象中有两个东西,你可能在上面看不到,但记住有这两个东西,上面的配置就是来配置这两个东西
// 1. actions
//       是一个对象,对象中的每一项都是一个 reducers中的动作函数(动作创建函数)
//       每一项 调用之后返回的就是一个同步的动作 {type: 'xxxx'},这里就返回{ type: 'count/inc' }
//       组件通过 dispatch(actions.xxx()) 代码会执行到 reducers.xxx 这个函数中去
//   	   组件通过 dispatch(actions.xxx(payload)) 代码会执行到 reducers.xxx 这个函数中去, 对应函数中 action.payload 就是这里传递过去的 payload
// 2. reducer
//       需要导出给到 configureStore 去使用
//	   每一个 reducer 都会接受到两个参数,state 与 action,

// 为了组件方便使用,具名导出切片的动作,给到组件使用来修改state数据,也可以只导出actions,在使用时通过actions.xxx(payload)来使用
export const inc = countSlice.actions.inc;
export const dec = countSlice.actions.dec;
//默认导出countSlice的reducer给主仓库的configureStore使用
export default countSlice.reducer;
  • 别忘了react-redux的Provider要在main.js中配置
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
//引入导出的主仓库
import store from "./store/index";

import App from "./App";
//使用react-redux 的 Provider包裹根组件,并绑定主仓库
ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);
  • 最后就是在组件中使用了
//引入react-redux的一系列方法
import { useSelector, useDispatch } from "react-redux";

// 引入切片导出的动作创建函数
import { inc, dec } from "./stores/count";    

function App() {
  //勾出仓库中count切片中的数据num
  const num = useSelector((state: any) => state.count.num);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>App - {num}</h1>
	//这里派发动作与之前有区别,括号不能省略,是通过这个函数来创建动作再派发过去,括号中如果写参数就会传入到payload中
      <button onClick={() => dispatch(inc())}>inc</button>
      <button onClick={() => dispatch(dec())}>dec</button>
    </div>
  );
}
export default App;

数据持久化

  • 手动做存储
    在仓库的reducers的方法中设置localStorage.setItem()
  • 使用第三方npm包
    npm install store2
//因为存只能字符串,取数据就要把字符串转成想要的格式
import store2 from 'store2'
//用store2可以免了转数据类型的各种操作,直接存,取出来就是存的时候的格式,store2帮我们做了转换的步骤
store2.set('flag',true)
store2.get('flag')
store2.set('obj',{sname:'张三',age:18})
store2.get('obj')
store2.session.set('msg',{name:'123',age:123})
store2.session.get('msg')

//使用自动持久化的库
安装npm i redux-persist
主仓库中

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import appReducer from './modules/app'
//导入自动持久化的API
// persistReducer(config,要操作持久化的reducer切片)、config:配置项
// persistStore(store)用于main.tsx中用PersistGate组件的persistor
import { persistReducer, persistStore } from 'redux-persist'
//导入存储到localStorage还是sessionStorage
import storage from 'redux-persist/lib/storage'
// import storage from 'redux-persist/lib/storage/session'这个表示存储到session

const rootReducer = combineReducers({
  //使用自动持久化的库,对有需要存储数据的切片库做持久化存储
  app:persistReducer({
    key:'app',//存储在storage的一个标识,前面会自动加上persist:
    storage,//这里配置存到localStorage还是sessionStorage
    blacklist:[]//黑名单,表示在该切片中不想存储的数据
  },appReducer)
})

const store = configureStore({
  reducer:rootReducer
})

export type RootState = ReturnType<typeof store.getState>
// 使用 persistStore 方法,生成 persistor,用于入口文件中 PersistGate 组件的 同名属性
export const persistor = persistStore(store);

export default store

在main.tsx中

import ReactDOM from 'react-dom/client'
import App from './App'
import { BrowserRouter as Router } from 'react-router-dom'
//导入仓库导出的自动化存储的工具persistor
import store,{ persistor } from './store'
//导入react-redux的Provider包裹根组件让根组件下的所有组件可以使用仓库
import { Provider } from 'react-redux'
//导入持久化存储库的PersistGate组件来包裹根组件
import { PersistGate } from 'redux-persist/integration/react'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <Provider store={store}>
    <PersistGate persistor={persistor}>
    <Router>
      <App />
    </Router>
    </PersistGate>
  </Provider>
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值