跳至主要內容

Redux

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

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)






























 
 
 



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