Redux

zfh前端框架React大约 8 分钟约 2299 字...

Redux 工作流

redux架构
redux架构
  • 所有数据的变化,必须通过派发action 来更新

  • action 是一个普通的 JavaScript 对象,用来描述跟操作内容

  • reducer 将传入的 stateaction 结合起来生成一个新的 state

在 react 项目中使用 redux

安装redux

npm install redux

基本使用

这里实现一个简单的 count 案例:

export function JIA_ACTION(num) {
    return {
        type: 'JIA_ACTION',
        num,
    }
}

接着创建 utils 文件夹, 创建2个文件:

  • connect.js(返回一个抽象 react redux 连接逻辑的高阶组件)
  • context.js(利用 context 全局共享 store)
import { PureComponent } from 'react'
import storeContext from './context'

export default function connect(mapStateToProps, mapDispatchToProps) {
  return function connectHOC(WrapperComponent) {
    class ReduxConnect extends PureComponent {
      constructor(props, context) {
        super(props, context)
        this.state = {
          storeState: mapStateToProps(context.getState()),
        }
      }

      render() {
        return (
          <div>
            <WrapperComponent
              {...this.props}
              {...mapStateToProps(this.context.getState())}
              {...mapDispatchToProps(this.context.dispatch)}
            />
          </div>
        )
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            storeState: mapStateToProps(this.context.getState()),
          })
        })
      }

      componentWillUnmount() {
        this.unsubscribe()
      }
    }

    ReduxConnect.contextType = storeContext
    return ReduxConnect
  }
}

在项目 index.js 文件中引入 store,利用 StoreContextProvider 组件,让 App 组件共享 store

import store from './redux/store'
import StoreContext from './redux/utils/context'
ReactDOM.render(
  <StoreContext.Provider value={store}>
    <App />
  </StoreContext.Provider>,
  document.getElementById('root')
)

在想要使用 store 的组件中定义mapStateToPropsmapDispatchToProps。把需要的statedispatch映射到想要使用 store 的组件的 props

import { PureComponent } from 'react'

import connect from './utils/connect'

import { JIA_ACTION } from './store/actionCreators'

const mapStateToProps = (state) => {
  return {
    count: state.count,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    add: function () {
      dispatch(JIA_ACTION(3))
    },
  }
}

const App = class extends PureComponent {
  componentDidMount() {
    console.log(this.props)
  }

  render() {
    return (
      <div>
        {this.props.counter}
        <button
          onClick={() => {
            this.props.add()
          }}
        >
          +3
        </button>
      </div>
    )
  }
}

const EnApp = connect(mapStateToProps, mapDispatchToProps)(App)

export default EnApp

虽然手动实现了connect帮助我们完成reduxreact 的连接,但是实际上 redux 官方提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效

  1. 安装:
npm install react-redux
  1. 改造之前的代码:

index.js 中将 Provider 组件替换成 react-redux 中的 Provider 组件

import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)



 
 
 



组件中将 connect 替换成 react-redux 中 connect

import { connect } from 'react-redux'
 

redux 中异步操作

事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给 redux 来管理

redux-异步操作
redux-异步操作

但是在 redux 中如何可以进行异步的操作呢?

  • 答案就是使用中间件
  • Koa框架中,Middleware 可以帮助我们在请求和响应之间嵌入一些操作的代码,比如 cookie 解析、日志记录、文件压缩等操作

理解中间件

redux 也引入了中间件Middleware的概念:

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

  • 我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件。官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk

redux-thunk 是如何做到让我们可以发送异步的请求呢?

  • 我们知道,默认情况下的 dispatch(action)action 需要是一个 JavaScript 的对象

  • redux-thunk 可以让action 可以是一个函数

  • 该函数会被调用,并且会传给这个函数一个 dispatch 函数和 getState 函数:

    • dispatch 函数用于我们之后再次派发 action
    • getState 函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

使用 redux-thunk

  1. 安装 redux-thunk:
npm install redux-thunk
  1. 在创建 store 时传入应用了 middlewareenhance 函数
    • 通过 applyMiddleware 来结合多个 Middleware, 返回一个 enhancer
    • enhancer 作为第二个参数传入到 createStore
import {applyMiddleware, createStore} from "redux"
import thunkMiddleware from 'redux-thunk'
import reducer from './reducer'
const storeEnhancer = applyMiddleware(thunkMiddleware)
const store = createStore(reducer, storeEnhancer)
export default store



 
 

  1. actionCreators.js定义一个返回函数的 action
  • 注意:这里不是返回一个对象了,而是一个函数
  • 该函数在 dispatch 之后会被执行
export function getDataAction() {
  return (dispath) => {
    {
      console.log('react-thuck数据接受成功')
      axios.get('https://www.imooc.com/api/http/search/suggest').then((res) => {
        // 我们依然需要触发相关的action,经过reducer的处理更新数据
        dispath(dataAction(res.data.data))
      })
    }
  }
}
  1. 映射该 actiondispatch 操作,和相关的 store 中的 state
const mapStateToProps = (state) => {
  return {
    data: state.list,
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    getData: function () {
      dispatch(getDataAction())
    },
  }
}
  1. componentDidMount 调用,就可以拿到数据
    componentDidMount() {
        this.props.getData()
    }
}

redux-devtools

利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等

  1. 在对应的浏览器中安装相关的插件,项目中安装这个包:
npm install --save @redux-devtools/extension
  1. 对 store 的 index.js 进行改造
import {applyMiddleware, createStore} from "redux"
import thunkMiddleware from 'redux-thunk'
import reducer from './reducer'
import { composeWithDevTools } from '@redux-devtools/extension';
const store = createStore(reducer,   composeWithDevTools(
    applyMiddleware(thunkMiddleware )
))
export default store



 
 
 


对于基本的 redux,只需要添加:

import { createStore } from 'redux';
import { devToolsEnhancer } from '@redux-devtools/extension';

const store = createStore(
  reducer,
  devToolsEnhancer()
);

Redux 代码文件拆分

随着项目的不断扩大,可能导致 store 结构越来越复杂,action 越来越多。所有有必要对代码结构进行拆分

代码结构如下:

├── store
│   ├── Count
│   │   ├── actionCreators.js
│   │   ├── constants.js
│   │   ├── index.js
│   │   └── reducer.js
│   ├── Data
│   │   ├── actionCreators.js
│   │   ├── constants.js
│   │   ├── index.js
│   │   └── reducer.js
│   ├── index.js
│   └── reducer.js	

data模块下代码:

import {LIST_DATA} from './constants'
import axios from "axios";

export function getList(listData) {
    return {
        type: LIST_DATA,
        listData
    }
}

// redux-thuck
export function getSomthing() {
    return (dispatch, getStore) => {
        axios.get('https://www.imooc.com/api/http/search/suggest').then(res => {
            console.log(res)
            dispatch(getList(res.data.data))
        })
    }
}

主模块代码:

import {counterReducer} from "./Count";
import {DataReducer} from "./Data";
export default function reducer(state = {}, action) {
    return {
        countStore: counterReducer(state.countStore, action),
        DataStore: DataReducer(state.DataStore, action),
    }
}



 
 
 
 

combineReducers 函数

目前我们合并的方式是通过每次调用 reducer 函数自己来返回一个新的对象

事实上,redux给我们提供了一个 combineReducers 函数可以方便的让我们对多个 reducer 进行合并:

import {counterReducer} from "./Count";
import {DataReducer} from "./Data";
import {combineReducers} from "redux"

const reducer = combineReducers({
    // reducer的目的就是为了返回state
    countStore: counterReducer,
    DataStore: DataReducer
})

export default reducer




 
 
 
 
 


RTK 官方推荐

Redux ToolKitopen in new window是目前官方推荐编写redux逻辑的方法

redux的编写逻辑过于繁琐和麻烦,RTK的目的就是解决这个问题

安装:

npm install @reduxjs/toolkit react-redux

基本使用

  1. 将之前的store代码结构作以调整
└── store
    ├── index.js
    └── moudles
        ├── count.js
        └── data.js
  1. 创建store
import {configureStore} from "@reduxjs/toolkit";
// 日志打印中间件,需要自己安装
import logger from 'redux-logger'
import  counterReducer  from "./moudles/count";
export default configureStore({
    // 传入多个reducer,或单个reducer函数
    reducer:{
        countStore:counterReducer
    },
    // 应用中间件|getDefaultMiddleware获取默认中间件在和我们要用的其他中间件concat返回中间件数组
  // 默认启用devtools和redux-thunk
  // middleware:[thunk, logger],
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
    // 默认开启devTools
    devTools:true
})
  1. 定义单个模块store
import {createSlice} from "@reduxjs/toolkit";

const countSlice=createSlice({
  // 显示在redux devtools中的名称
    name:'count',
  // 初始值
    initialState:{
        count:1000,
    },
  // 里面定义各种action
    reducers:{
        addNumber(state,{payload}){
          // 在这里直接可以修改state
            state.count=state.count+payload
        }
    }
})

export const {addNumber}=countSlice.actions

export default countSlice.reducer
  1. RTK仅仅是简化了rdux的逻辑编写,而连接仍需要通过react-redux,和之前的写法是一样的

异步处理

  1. 引入createAsyncThunk,使用该api创建一个thunk,创建时第一个参数显示在devtools,表示是哪一个thunk,第二个函数用于异步数据的获取,必须返回一个promise
  2. createSlice extraReducers选项,操作state。有两种方式对象或者函数,官方推荐使用函数(使用对象貌似会导致ts使用比较麻烦,还没用到)。fulfilled表示数据成功获取到需要执行的逻辑
import {createSlice} from "@reduxjs/toolkit";
import {createAsyncThunk} from "@reduxjs/toolkit";
import axios from "axios";

const getListData = createAsyncThunk('getListData', async (num) => {
  //组件调用传递过来的参数
    console.log(num)
    const res = await axios.get('https://www.imooc.com/api/http/search/suggest')
    return res.data.data
})
const dataSlice = createSlice({
    name: 'data',
    initialState: {
        list: [],
    },
    reducers: {},
    extraReducers: {
        [getListData.fulfilled](state, {payload}) {
          // 在此直接操作state
            state.list=payload
        },
    }
    // extraReducers:(builder)=>{
    //     builder.addCase(getListData.fulfilled,(state,{payload})=>{
    //         state.list=payload
    //     })
})

export {getListData}

export default dataSlice.reducer

 


 
 
 
 






 
 
 
 
 
 
 










  1. 跟普通的aciton一样在组件中进行映射,消费:
import {connect} from "react-redux";
import {addNumber} from "./store/moudles/count";
import {PureComponent} from "react";
import {getListData} from "./store/moudles/data";
class App extends PureComponent{
    componentDidMount() {
        this.props.getList('123')
    }

    render() {
        return (
            <div onClick={this.props.add}>
                {this.props.count}
                <ul>
                    {
                        this.props.list.map(item=><li>{item.word}</li>)
                    }
                </ul>
            </div>
        );
    }
}
const mapStateToProps=state=>({
    count:state.countStore.count,
    list:state.dataStore.list
})
const mapDispatchToProps=dispatch=>({
    add(){
        dispatch(addNumber(3))
    },
    getList(num){
        dispatch(getListData(num))
    }
})

export default connect(mapStateToProps,mapDispatchToProps)(App);






























 
 
 



上次编辑于:
本站勉强运行 小时
本站总访问量
網站計數器