Redux笔记

简介

redux基本实现参考:redux使用和基本实现

react-redux

react项目中一般配合react-redux使用

基本使用如下:

  • 安装:npm i redux react-redux --save

  • src/目录下新建store/目录

  • src/store/目录下新建actionType.js文件(actiontype集中管理,小项目没有必要)

export const ADD_TODO = "ADD_TODO"; export const TOGGLE_TODO = "TOGGLE_TODO"; export const SET_FILTER = "SET_FILTER";
  • src/store/目录下新建actions.js文件,存放所有的action
import * as actionTypes from './actionType' let nextId = 0 export const addTodo = content => ({ type: actionTypes.ADD_TODO, payload: { id: ++nextId, content } }) export const toggleTodo = id => ({ type: actionTypes.TOGGLE_TODO, payload: { id } }) export const setFilter = filter => ({ type: actionTypes.SET_FILTER, payload: { filter } })
  • src/store/目录下新建reducers/目录,存放所有的reducer

  • src/store/reducers/目录下新建reducer文件,比如todos.js

import { ADD_TODO, TOGGLE_TODO } from '../actionType' // state的初始值 const initialState = { allIds: [], byIds: {} } export default function (state = initialState, action) { switch(action.type) { case ADD_TODO: { const { id, content } = action.payload return { // 用这种展开的语法,不去修改原始值,而是直接返回一个新值 allIds: [...state.allIds, id], byIds: { ...state.byIds, [id]: { content, completed: false } } } } case TOGGLE_TODO: { const { id } = action.payload return { ...state, byIds: { ...state.byIds, [id]: { ...state.byIds[id], completed: !state.byIds[id].completed } } } } default: return state } }
  • src/store/reducers/目录下新建index.js文件,合并所有的reducer
import { combineReducers } from 'redux' import todos from './todos' import visibilityFilter from './visibilityFilter' export default combineReducers({ todos, visibilityFilter })
  • src/store/目录下新建index.js文件,生成store
import { createStore } from 'redux' import rootReducer from './reducers' export default createStore(rootReducer)
  • 修改src/App.js文件
// ... import { Provider } from 'react-redux' import store from './store' // ... function App() { return ( <Provider store={store}> {/* ... */} </Provider> ) } export default App;
  • 在组件中使用connect
// ... import { connect } from 'react-redux' import { toggleTodo } from '../../store/actions' // ... const Todo = ({ todo, toggleTodo }) => ( {/* ... */} ) export default connect(null, { toggleTodo })(Todo)

备注

  • connect高级组件处理之后,在props中就有对应的stateaction
  • connect的第一个参数是将state转为props的方法mapStateToProps,当然也可以直接传对象
  • connect的第二个参数是将dispatch转为props的方法mapDispatchToProps,当然也可以直接传对象
// 只传第一个参数 const mapStateToProps = state => { const { visibilityFilter } = state const todos = getTodosByVisibilityFilter(state, visibilityFilter) return { todos } } export default connect(mapStateToProps)(TodoList) // 传两个参数 import { connect } from 'react-redux' import { toggleTodo } from '../actions' const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } } const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList)

redux-devtools

  • redux-devtools是谷歌浏览器的插件,方便调试

  • 需要在代码中打开

  • 修改src/store/index.js文件

// ... export default createStore(rootReducer, window.devToolsExtension ? window.devToolsExtension() : f=>f)

使用修饰器

create-react-app项目为例:

  • 如果是babel7,需要下载babel-plugin-transform-decorators-legacynpm i babel-plugin-transform-decorators-legacy --save

  • 修改package.json文件

{ // ... "babel": { // ... "plugins": [ "transform-decorators-legacy" // ... ] }, // ... }
  • 如果是babel7,需要下载@babel/plugin-proposal-decoratorsnpm i @babel/plugin-proposal-decorators --save

  • 修改package.json文件

{ // ... "babel": { // ... "plugins": [ // ... ["@babel/plugin-proposal-decorators", { "legacy": true }], ] }, // ... }
  • 在组件中connect就可以使用修饰器格式了。其他的,比如withRouter都可以用修饰器格式
// ... @connect( ({ test }) => ({ count: test.count}), { testCountAdd } ) class Demo extends React.Component { // ... } export default Demo

redux-trunk

  • 安装:npm i redux-thunk --save

  • 使用applyMiddleware添加中间件

  • 使用compose合并中间件和redux-devtools(只有中间件的话,就没必要用这个方法了)

  • 修改src/store/index.js文件

import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import rootReducer from './reducers' export default createStore(rootReducer, compose( applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f=>f ))
  • 修改src/store/action.js,添加异步方法
// ... export const testCountAddAsync = num => { return dispatch => { setTimeout(() => { dispatch(testCountAdd(num)) }, 2000) } } // ...
  • 在组件中使用
@connect( ({ test }) => ({ count: test.count}), { testCountAddAsync } ) class Demo extends React.Component { // ... } export default Demo

redux-actions

redux-actions简化了action的编写

  • 安装:npm i redux-actions --save

  • 修改src/store/actions.js文件

import { createActions } from 'redux-actions' export default createActions({ APP: { ADD_TODO: undefined, TOGGLE_TODO: undefined, SET_FILTER: undefined, }, USER: { LOGIN: undefined, LOGOUT: undefined, SET_USER_ROLE: undefined, }, })
  • 删除src/store/actionType.js文件

  • 修改src/store/reducers/todos.js文件

import { handleActions } from 'redux-actions' import Actions from '../actions' // state的初始值 const initialState = { allIds: [], byIds: {} } export default handleActions({ [Actions.app.addTodo](state, { payload }) { const { id, content } = payload return { // 用这种展开的语法,不去修改原始值,而是直接返回一个新值 allIds: [...state.allIds, id], byIds: { ...state.byIds, [id]: { content, completed: false } } } }, [Actions.app.toggleTodo](state, { payload }) { const { id } = payload return { ...state, byIds: { ...state.byIds, [id]: { ...state.byIds[id], completed: !state.byIds[id].completed } } } }, }, initialState)
  • 修改src/store/index.js文件,添加actions导出
// ... export { default as Actions } from './actions'
  • 使用示例
import { useSelector, useDispatch } from 'react-redux' import Actions from '@/store/actions' export default function Demo(props) { const app = useSelector(({ app }) => app) const dispatch = useDispatch() const onTodoClick = id => { dispatch(Actions.app.toggleTodo({ id })) } // ... }

redux-saga

一般有两个步骤:1、异步请求接口;2、将请求结果存到store中
使用的是ES6generator语法

  • 安装:npm i redux-saga --save

  • 修改src/store/actions.js文件,添加action

import { createActions } from 'redux-actions' // 建议区分开 action 的用途,方便后期维护 // ON_XXX 开头的给 saga 用 // 其他的,比如 SET_XXX、ClEAR_XXX 等给 reducer 用 export default createActions({ // ... DEMO: { ON_USER_INFO: undefined, SET_USER_INFO: undefined, } })
  • src/store/reducer/目录下新建demo.js文件
import { handleActions } from 'redux-actions' import Actions from '../actions' const INITIAL_STATE = { userInfo: null, } export default handleActions({ [Actions.demo.setUserInfo](state, { payload }) { return { ...state, userInfo: payload } }, }, INITIAL_STATE)
  • 修改src/store/reducer/index.js文件,添加demo
import { combineReducers } from 'redux' // ... import demo from './demo' export default combineReducers({ // ... demo, })
  • src/store/目录下添加saga/目录

  • src/store/saga/目录下添加demo.js文件

import { all, takeLatest, put } from 'redux-saga/effects' import Actions from '../actions' // 延时,用来模拟请求 export const delay = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, time); }); }; // 模拟请求 const request = async (url, options) => { console.log(url, options) await delay(1000) return { code: 0, data: { name: 'wmm66', age: 18, } } } export function* fetchUserInfo({ payload }) { const { id } = payload || {} const res = yield request('user/info', { id }) if (res && res.code === 0) { yield put(Actions.demo.setUserInfo(res.data)) } } export default all([ takeLatest(Actions.demo.onUserInfo, fetchUserInfo), ])
  • src/store/saga/目录下添加index.js文件
import { all } from 'redux-saga/effects' import demoSaga from './demo' export default function* rootSage() { yield all([ demoSaga ]) }
  • 修改src/store/index.js文件
import { createStore, applyMiddleware, compose } from 'redux' import createSagaMiddleware from 'redux-saga' import rootReducer from './reducer' import rootSaga from './saga' const sagaMiddleware = createSagaMiddleware() const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize... }) : compose; const middlewares = [sagaMiddleware]; // if (process.env.NODE_ENV === 'development') { // middlewares.push(require('redux-logger').createLogger()); // } const enhancer = composeEnhancers(applyMiddleware(...middlewares)) const store = createStore(rootReducer, enhancer); sagaMiddleware.run(rootSaga); export default store export { default as Actions } from './actions'
  • 页面中使用
import React from 'react' import { useSelector, useDispatch } from 'react-redux' import Actions from '@/store/actions' export default function Test(props) { const { userInfo } = useSelector(({ demo }) => demo) const dispatch = useDispatch() const onFetchUser = () => { dispatch(Actions.demo.onUserInfo({ id: 123 })) } return ( <div> <button onClick={onFetchUser}>请求数据</button> <div>{JSON.stringify(userInfo)}</div> </div> ) }

扩展

有的时候,我们需要在某些异步操作结束后(比如接口请求完成后)做某些操作

使用Promise的时候,我们可以使用thenawait等很方便的做到

generator语法可以使用callback的方式实现

基本代码如下:

// src/store/saga/demo.js文件 export function* fetchUserInfo({ payload }) { const { id, callback } = payload || {} const res = yield request('user/info', { id }) if (res && res.code === 0) { yield put(Actions.demo.setUserInfo(res.data)) callback && callback(res.data) } } // 使用 const onFetchUser = () => { dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => { console.log(userInfo) } })) }

我们可以将其Promise化,方便使用

const promisifySaga = () => { return new Promise((resolve, reject) => { dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => { resolve(userInfo) } })) }) } const onFetchUser = async () => { const userInfo = await promisifySaga() console.log(userInfo) }
  • promisifySaga应该是一个比较通用的方法,我们通过传参来实现
function promisifySaga({dispatch, action, params = {}}) { return new Promise((resolve, reject) => { if (!dispatch || !action || typeof action !== 'function') { reject('参数错误') return } dispatch(action({ ...params, callback: (res) => { resolve(res) } })) }) } const onFetchUser = async () => { const userInfo = await promisifySaga({ dispatch, action: Actions.demo.onUserInfo, params: { id: 123 } }) console.log(userInfo) }
  • promisifySaga的意思是将sagapromise化,我们希望这么调用promisifySaga({dispatch, action})({ id: 123 })
function promisifySaga({dispatch, action}) { return (params = {}) => { return new Promise((resolve, reject) => { if (!dispatch || !action || typeof action !== 'function') { reject('参数错误') return } dispatch(action({ ...params, callback: (res) => { resolve(res) } })) }) } } const onFetchUser = async () => { const userInfo = await promisifySaga({ dispatch, action: Actions.demo.onUserInfo })({ id: 123 }) console.log(userInfo) }
  • 最终代码如下
// src/store/saga/demo.js export function* fetchUserInfo({ payload }) { const { id, callback = i => i, } = payload || {} // 这里推荐捕捉错误,往后抛 try { const res = yield request('user/info', { id }) if (res && res.code === 0) { yield put(Actions.demo.setUserInfo(res.data)) callback({ status: 'success', data: res.data }) } else { callback({ status: 'error', reason: res.msg }) } } catch(err) { callback({ status: 'error', reason: err }) } } // src/lib/utils.js import store from '@/store' /** * 是通过回调函数实现的 * 如果想在异步执行后执行某些操作,对应的saga必须设置callback * 成功:callback({ status: 'success', data: res.data }) * 失败:callback({ status: 'error', reason: err }) */ export const promisifySaga = action => { return (params = {}) => { return new Promise((resolve, reject) => { if (!action || typeof action !== 'function') { reject('参数错误') return } store.dispatch(action({ ...params, callback: ({ status, data, reason }) => { status === 'success' ? resolve(data) : reject(reason) }, })) }) } } // 代码中使用 import React from 'react' import { useSelector, useDispatch } from 'react-redux' import Actions from '@/store/actions' import { promisifySaga } from '@/lib/utils' export default function Test(props) { const { userInfo } = useSelector(({ demo }) => demo) const dispatch = useDispatch() const onFetchUser = () => { const userInfo = await promisifySaga(Actions.demo.onUserInfo)({ id: 123 }) console.log(userInfo) } return ( <div> <button onClick={onFetchUser}>请求数据</button> <div>{JSON.stringify(userInfo)}</div> </div> ) }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: