React-Redux使用

redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux

一、 Redux三大核心理念

  • Store :定义数据state 只读
  • action :通过action来更新数据
  • reducer: 纯函数来执行修改

1.1 什么是 Redux Toolkit?

  • 官方推荐的编写 Redux 逻辑的方法。@reduxjs/toolkit 包封装了核心的 redux 包,包含我们认为构建 Redux 应用所必须的 API 方法和常用依赖。 Redux Toolkit 集成了我们建议的最佳实践,简化了大部分 Redux 任务,阻止了常见错误,并让编写 Redux 应用程序变得更容易
  • 要写任何的 Redux 逻辑,你都应该使用 Redux Toolkit 来编写代码
  • 创建 Redux Toolkit 来消除手写 Redux 逻辑中的「样板代码」,防止常见错误,并提供简化标准 Redux 任务的 API
  • 创建 Redux Toolkit 来消除手写 Redux 逻辑中的「样板代码」,防止常见错误,并提供简化标准 Redux 任务的 API

1.2 redux基本使用

  • 使用npm install redux 安装redux

简单基本示例使用

//store.js
// 进行引入
const { createStore } = require("redux")

// 定义初始化数据
const initState = {
 name: "林夕",
 age: 18
}
// 定义reducer函数:纯函数
function reducer(state = initState, action) {
 // 参数state:store目前保存的state
 // 参数action:本次次需要更新Waction(dispatchi入iaction)
 // console.log(state, action);
 // 有数据更新进行更新,返回新state
 switch (action.type) {
   case "name_change":
     return { ...state, name: action.name }
   case "age_change":
     return { ...state, age: action.age }
   default:
     return state;
 }


 // 返回值就是本次存储的initState值:即store值
 return state
}
// 创建store
const store = createStore(reducer)

module.exports = store

逐步优化代码,监听订阅修改使用subscribe

1.3 subscribe(listener)监听数据是否变化

变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你可以在回调函数里调用 getState() 来拿到当前 state

1.3.1 参数

  1. listener (Function): 每当 dispatch action 的时候都会执行的回调。state 树中的一部分可能已经变化。你可以在回调函数里调用getState() 来拿到当前 state。store 的 reducer 应该是纯函数,因此你可能需要对 state 树中的引用做深度比较来确定它的值是否有变化。

1.3.2返回值

(Function): 一个可以解绑变化监听器的函数。

const store = require("./store");

// 定义动态生成action可以新建新文件---始
//这里的type名字同样可以进行抽取使用。
const nameActionDispatch = (name) => ({ type: "name_change", name })
const ageActionDispatch = (age) => ({ type: "age_change", age })
// 定义动态生成action可以新建新文件---末

//订阅监听 
const unsubscribe = store.subscribe(() => {
  console.log("监听修改", store.getState());
})

// 修改store中的数据:必须action
store.dispatch(nameActionDispatch("Go to swimming pool"))
store.dispatch(ageActionDispatch(22))
//取消订阅监听
unsubscribe()
store.dispatch(nameActionDispatch("取消订阅"))
  • 其上代码我们可以进行拆分逻辑抽取单独文件进行优化,做到编写一次复用N次进行优化。
  • 同时遵从三大核心理念进行拆分文件,有需要可以拆分一个常量文件用来导出type名;
tips
  • 注意:node中对ES6模块化的支持
  • 从node v13.2.0开始,node才对ES6模块化提供了支持
  • node v13.2.0之前,需要进行如下操作:
    • 在package.json中添加属性:"type":"module”;
    • 在执行命令中添加如下选项:node --experimental-modules src/index.js;
    • node v13.2.0之后,只需要进行如下操作:在package.json中添加属性:"type":"module”;
  • 注意:导入文件时,需要跟上.js后缀名:

二、 react中初次使用redux实例练习

  • 使用create-react-app '项目名' 创建项目
  • 使用npm install redux 安装redux

新建目录

APP.jsx文件

import React, { PureComponent } from 'react'
import Home from './peages/Home'
import User from './peages/User'
import { HOC } from './utils/higherOrderComponent'

const Homes = HOC(Home)
const Users = HOC(User)

// 这里懒得拆分单文件了,使用函数创建了一个组件
const Header = HOC((props)=>{
  return(<h2>我是APP组件:{props.count}</h2>)
})

export class App extends PureComponent {
  render() {
    return (

      <>
        <Header/>
        <Homes />
        <div>
          <Users />
        </div>
      </>
    )
  }
}

export default App

Home.jsx文件子组件

import React, { PureComponent } from 'react'
import store from '../store'
import { addNumAction } from '../store/actionCreators'

export class Home extends PureComponent {
  add(num){
    store.dispatch(addNumAction(num))
  }
  render() {
    //高阶组件进行拦截后的传输参数
    const { count } = this.props
    return (
      <>
        我是home增加计数:{ count }
        <div>
          <button onClick={e=>this.add(1)}>+1</button>
          <button onClick={e=>this.add(5)}>+5</button>
          <button onClick={e=>this.add(10)}>+10</button>
        </div>
      </>
    )
  }
}

export default Home

User.jsx文件子组件

import React, { PureComponent } from 'react'
import store from '../store'
import { reduceNumAction } from '../store/actionCreators'

export class User extends PureComponent {
  reduceClick(num) {
    store.dispatch(reduceNumAction(num))
  }
  render() {
    //高阶组件进行拦截后的传输参数
    const { count } = this.props
    return (
      <>
        我是User减少计数:{count}
        <div>
          <button onClick={e => this.reduceClick(1)}>-1</button>
          <button onClick={e => this.reduceClick(5)}>-5</button>
          <button onClick={e => this.reduceClick(10)}>-10</button>
        </div>
      </>
    )
  }
}

export default User

higherOrderComponent.js高阶组件不明白高阶组件的使用请移步React的高阶组件

import {PureComponent} from 'react'
import store from '../store'
// 这里封装了一个高阶组件,相当进行拦截操作。
export function HOC(WrappedComponent){
  class NewWrappedComponent extends PureComponent{
    constructor() {
      super()
      this.state = {
        count:store.getState().counter
      }
    }
    componentDidMount() {
      store.subscribe(() => {
        const state = store.getState()
        //实现更新因为这里高阶组件使用setState数据变化都会进行更新界面
        this.setState({count:state.counter})
      })
    }
    render(){
      return(
        <>
          <WrappedComponent {...this.props} {...this.state}/>
        </>
      )
    }
  }
  return NewWrappedComponent
}

redux结构划分

源自coderwhy学习

store/index.js入口文件

import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)

export default store

reducer.js 纯函数来执行修改state

import { ADD_COUNTER, REDUCE_COUNTER } from './constants'

const initState = {
  counter: 10
}

function reducer(state = initState, action) {
  switch (action.type) {
    case ADD_COUNTER:
      let counter = state.counter + action.counter
      return { ...state, counter }
    case REDUCE_COUNTER:
      let reduceCounter = state.counter - action.counter
      return { ...state, counter: reduceCounter }
    default:
      return state;
  }
}

export default reducer

actionCreators.js执行action函数抽取单文件

import { ADD_COUNTER,REDUCE_COUNTER } from './constants'
export const addNumAction = (counter) => ({ type: ADD_COUNTER, counter })
export const reduceNumAction = (counter) => ({ type: REDUCE_COUNTER, counter })

constants.js 用来存储改type

export const ADD_COUNTER = "add_counter"
export const REDUCE_COUNTER = "reduce_counter"

三、 React-Rudex

上示例中使用了redux,但是对于监听store数据改变 派发事件(dispatch),等这些操作出现公共复杂引入调用,redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效

npm install react-redux

1 .使用 Provider 透传 Store

使用 <Provider> 组件包裹 <App> 组件,并将 Redux store 作为 prop 传递给 <Provider> 组件。之后,应用程序中的每个组件都可以在需要时能够访问到 Redux store 

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from "react-redux"
import store from './store'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    // 使用 `<Provider>` 组件包裹 `<App>` 组件  
    // 并把 Redux store 作为 prop 传入
    <Provider store={store}>
      <App />
    </Provider>

  </React.StrictMode>
);

2. connect()高阶函数

connect() 函数将 React 组件连接到 React store。

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

  • mapStateToProps 和 mapDispatchToProps 分别处理 Redux store 的 state 和 dispatchstate 和 dispatch 将作为第一个参数提供给 mapStateToProps 或 mapDispatchToProps 函数。
  • 在内部,mapStateToProps 和 mapDispatchToProps 的返回值分别称为 stateProps 和 dispatchProps。如果定义了,它们将作为第一个和第二个参数提供给 mergeProps,其中第三个参数将是 ownProps。组合的结果通常称为 mergedProps,将提供给连接的组件。
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { powerNumAction } from '../store/actionCreators'

export class Home extends PureComponent {
  poweClick(num){
    this.props.powerNumAction(num)
  }
  render() {
    const { counter } = this.props
    return (
      <>
      Home账号文章:{counter}
      <div>
          <button onClick={e => this.poweClick(2)}>*2</button>
          <button onClick={e => this.poweClick(3)}>*3</button>
          <button onClick={e => this.poweClick(4)}>*4</button>
        </div>
      </>
    )
  }
}

//像高阶组件传输参数一样进行编写上文使用
const mapStateToProps = (state) => ({ counter: state.counter })

const mapDispatchToProps = (dispatch) => {
  return {
    // dispatch 普通的 action
    //来源参考下图
    powerNumAction: (num) => dispatch(powerNumAction(num)),
  }
}

//connect()返回值是一个高阶组件,
export default connect(mapStateToProps,mapDispatchToProps)(Home)

上文用例也可用Hooks来使用,但是要在函数式组件中使用

更新数据同时可以使用异步请求获取数据存储在state中:异步获取数据一般在生命周期componentDidMount中进行操作一般做法和上例一样。

优化后使用中间件来进行操作数据转发

3.、 异步数据管理redux-thunk

须知:异步请求操作数据一般用在网络请求等应用场景,因此我们需要一个中间件来进行异步操作。

  • 这个中间件的目的是在dispatch的action最终达到的reducer之间,扩展一些自己的代码;
  • 比如日志记录、调用异步接口、添加代码调试功能等等;

安装:npm install redux-thunk

  • 上文使用dispatch({...})是需要进行传入对象
  • 但是需要在store.dispatch(function)中派发函数:
    • 本质上使用redux-thunk工具库会对dispatch进行拦截,该库会进行判断传入的参数具体是什么类型,若是函数就进行执行函数
  • 进行网络请求一般进行抽取,但是上文提出是在组件中进行网络请求后进行修改,这里提出示例进行抽取
  • 放在redux中进行数据请求后分发

使用applyMiddleware | Redux 中文官网

因此使用redux-trunk做为中间件使用

store/index.js

import { createStore,applyMiddleware } from "redux";
import {thunk} from 'redux-thunk'
import { reducer } from "./reducer";

const store = createStore(reducer,applyMiddleware(thunk))
export default store

store/actionCreators.js

import { COUNT_CHANGE,ARRAY_CHANGE } from "./constants";
import axios from 'axios'
export const countChange=(count)=>({type:COUNT_CHANGE,count})
export const arrayChange=(arrayList)=>({type:ARRAY_CHANGE,arrayList})
export const fetchNetData=()=>{
  
  return function(dispatch, getState){
    axios.get("接口地址").then(({data})=>{
      console.log(data);
      dispatch(arrayChange(data.data.list))
    })
  }
}

其它调用逻辑都是和上文介绍相同

4、combineReducers(reducers)

当我们在项目中如果多个模块使用数据状态管理那么就需要多个文件进行拆分不同store最后再合并成一个出口去使用,类似于vuex中的模块化管理类似。需要用到该函数

随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分

由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名

5. redux-devtools 在redux中继承devtools

import { createStore,applyMiddleware,compose, combineReducers } from "redux";
import {thunk} from 'redux-thunk'
import { reducer } from "./reducer"
//开启redux工具调试
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
//这里只演示了单个作为模块使用,多个的话可以继续对象追加包括,取用也需要用此名字
const rootReducer = combineReducers({count: reducer})//像vue中模块化管理类似需要进行包括一层
const store = createStore(rootReducer,composeEnhancers(applyMiddleware(thunk)))
export default store

四、 Redux Toolkit 

本文知识是对前两章文章的内容的简写方式,前两章是redux的基础使用。

@reduxjs/toolkit包仅仅是对redux的代码进行封装;在react中使用还是需要用react-redux包将两者联系在一起

npm install @reduxjs/toolkit react-redux

  • Redux Toolkit的核心API主要是如下几个
    • configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
    • createSlice: 接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions.
    • createAsyncThunk: 接受一个动作类型字符电和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: {}
})

1 .createSlice

createSlice 接收一个包含三个主要选项字段的对象:

createSlice返回值是一个对象,包含所有的actions

  • name:用户标记slice的名词字符串,将用作生成的 action types 的前缀
  • initialState:reducer 的初始 state初始化值
  • reducers:相当于reducer函数
    • 对象类型,并且可以添加很多的函数
    • 函数类似于redux原来reducer中的一个case语句
    • 函数的参数
      • 参数一:state
      • 参数二:调用这个action时,传递的action参数;

2.configureStore

用于创建store对象,常见参数如下:

  • reducer,将slice中的reducer可以组成一个对象传入此处
  • middleware: 可以使用参数,传入其他的中间件
  • devTools: 是否配置devTools工具,默认为true;

app.jsx组件

import React, { PureComponent } from 'react'
import Home from './Home'
import { connect } from 'react-redux'
import { ageChange, nameChange } from './store/userState'

export class App extends PureComponent {
  render() {
    const { name, age } = this.props
    return (
      <>
        <h2>我是APp组件 :{name}-{age}</h2>
        <button onClick={e=>this.props.nameChange("测试一下")}>修米关系</button>
        <button onClick={e=>this.props.ageChange(123)}>修米关系2</button>
        <Home />
      </>
    )
  }
}
const mapStateToProps = (state) => ({
  name: state.userState.name,
  age: state.userState.age,
})
const mapDispatchToProps = (dispatch) => {
  return {
    nameChange: (name) => dispatch(nameChange(name)),
    ageChange: (num) => dispatch(ageChange(num)),
  }
}
export default connect(mapStateToProps,mapDispatchToProps)(App) 

store/index.js

import { configureStore } from '@reduxjs/toolkit'
import userState from './userState'


export default configureStore({
  reducer: {userState}
})

store/userState.js

import { createSlice } from '@reduxjs/toolkit'

const userStateSlice = createSlice({
  name: "useState",
  initialState: {
    name: "林夕",
    age: 18
  },
  reducers: {
    nameChange(state, action) {
      state.name = action.payload
    },
    ageChange(state, action) {
      state.age = action.payload
    }
  }


})
export const { nameChange, ageChange } = userStateSlice.actions

export default userStateSlice.reducer

3.createAsyncThunk :Redux Toolkit的异步操作

  • 当createAsyncThunk创建出来的action被dispatch时,会存在三种状态
    • pending: action被发出,但是还没有最终的结果
    • fulfilled: 获取到最终的结果 (有返回值的结果)
    • rejected: 执行过程中有错误或者抛出了异常

createSlice 还接收一个叫 extraReducers 的选项,可以让同一个 slice reducer 监听其他 action types。这个字段应该是一个带有 builder 参数的回调函数,我们可以调用 builder.addCase(actionCreator, caseReducer) 来监听其他 actions 用法一:官方推荐

用法二

4.手写react-redux中的 connect()函数

import { PureComponent } from "react";
import store from '../store'

export default function customConnet(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    class NewCom extends PureComponent {
      constructor(props) {
        super(props)
        this.state = mapStateToProps(store.getState())
      }

      componentDidMount() {
        this.unsubscribe = store.subscribe(() => {
          this.setState(mapStateToProps(store.getState()))
          // this.forceUpdate()
        })
      }
      componentWillUnmount() {
        this.unsubscribe()
      }
      render() {
        const state = mapStateToProps(store.getState())
        const dispatch = mapDispatchToProps(store.dispatch)
        return <WrappedComponent {...this.props} {...state}{...dispatch} />
      }
    }
    return NewCom
  }
}

页面组件

import React, { PureComponent } from 'react'
import customConnet from './HOC/customConnet'
import { nameChange } from './store/userState'
export class Custom extends PureComponent {
  render() {
    const { name } = this.props
    return (
      <div>
        {name}
        <button onClick={e=>this.props.nameChange("我是自定义组件高阶")}>我是自定义高阶函数修改</button>
      </div>
    )
  }
}
const mapStateToProps = (state) => ({
  name: state.userState.name,
})
const mapDispatchToProps = (dispatch) => {
  return {
    nameChange: (name) => dispatch(nameChange(name)),
  }
}
export default customConnet(mapStateToProps, mapDispatchToProps)(Custom) 

对于导入的import store from '../store'文件耦合性太高我们可以进行一些优化使用前文的context上下文来传递store路径的解耦

4.1 HOC/hocContext.js

import { createContext } from "react";
export const HOCContext = createContext()

4.2 HOC/customConnet.js

import { PureComponent } from "react";
import { HOCContext } from "./hocContext";

export function customConnet(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    class NewCom extends PureComponent {
      constructor(props, context) { 
        super(props)
        this.state = mapStateToProps(context.getState())
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState(mapStateToProps(this.context.getState()))
          // this.forceUpdate()
        })
      }
      componentWillUnmount() {
        this.unsubscribe()
      }
      render() {
        const state = mapStateToProps(this.context.getState())
        const dispatch = mapDispatchToProps(this.context.dispatch)
        return <WrappedComponent {...this.props} {...state}{...dispatch} />
      }
    }
    NewCom.contextType = HOCContext
    return NewCom
  }
}

4.3 HOC/index.js

export { customConnet } from "./customConnet"
export { HOCContext } from "./hocContext"

4.4 src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './store'
import { HOCContext } from "./HOC";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <HOCContext.Provider value={store}>
      <App />
    </HOCContext.Provider>
  </React.StrictMode>
);


5、Redux Toolkit的数据不可变性★

  • 无论是类组件中的state,还是redux中管理的state;
  • 在整个JavaScript编码过程中,数据的不可变性都是非常重要的;
  • 前面的章节示例中使用了很多的类似浅拷贝的数据操作,但是尽管如此也可能会出现问题
    • 比如过大的对象,进行浅拷贝也会造成性能的浪费;
    • 比如浅拷贝后的对象,在深层改变时,依然会对之前的对象产生影响;
  • 事实上Redux Toolkit底层使用了immerjsimmutable的一个库来保证数据的不可变性
  • 为了节约内存,又出现了一个新的算法: Persistent Data Structure(持久化数据结构或一致性数据结构);
    • 用一种数据结构来保存数据
    • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费

五、react中state管理

  1. 方式一:组件中自己的state管理
  2. 方式二:Context数据的共享状态
  3. 方式三:Redux管理应用状态 建议使用
  • UI相关的组件内部可以维护的状态,在组件内部自己来维护:
  • 大部分需要共享的状态,都交给redux来管理和维护:
  • 从服务器请求的数据(包括请求的操作),交给redux来维护:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值