React之redux学习日志(redux/react-redux/redux-saga)
redux官方中文文档:https://www.redux.org.cn/docs/introduction/CoreConcepts.html
react-redux Dome:https://codesandbox.io/s/react-redux-e1el3(需FQ才能访问)
1. Redux工作流程图:
2. redux三大原则:
1. 单一数据源:在Redux中有且只能有一个 state 仓库
2. State是只读的: state仓库的数据只能读取,不能进行修改
3. 使用纯函数执行修改:reducer中,应该返回一个纯函数,函数接受先前的 state和action, 然后返回一个新的 state
3. Redux 搭配 React 使用
安装:
npm install --save react-redux
3.1. react-redux在React中的使用方式
· 在react入口文件中注入Redux
import React from 'react' import ReactDOM from 'react-dom' import RouterConfig from '@/Router' import { Provider } from 'react-redux' import store from '@/store' const App = () => ( <div> <!-- Provider 让所有容器组件都可以访问 store --> <Provider store={store}> <RouterConfig/> </Provider> </div> ) const domContainer = document.querySelector('#app') ReactDOM.render(<App />, domContainer)
· 根目录中新建store目录,并且添加:index.js、 reducer.js、create-action、action-type
index.js文件
import 'babel-polyfill' // es6解析 import { createStore, compose, applyMiddleware } from 'redux' import reducer from './reducer' // redux-dev-tools工具配置 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose const store = createStore( reducer, composeEnhancers( applyMiddleware( // 这里可以放一些中间件,如 redux-saga 等 ) ) ) export default store
create-action.js / action-type.js
// action-type: 一般统一在这个文件中定义 action 的类型,方便管理 export const GET_USERINFO_ACTION = 'GET_USERINFO_ACTION' // create-action:每个action都返回一个纯对象,type是约定必须 import { GET_USERINFO_ACTION } from './action-type' export const getUserInfoAction = (value) => ({ type: GET_USERINFO_ACTION, value })
reducer.js
import { GET_USERINFO_ACTION } from './action-type'
// 创建一个默认的仓库,一般提出来会更加清晰
const defaultState = { userInfo: {} }
// reducer必须是一个纯函数 const reducer = (state=defaultState, action) => { const { type } = action
// 深拷贝,一定不能直接对state进行修改 const newState = JSON.parse(JSON.stringify(state)) if (type === GET_USERINFO_ACTION){ newState.userInfo = action.value } return newState }
至此,仓库已经搭建完毕,接下来是在react中进行使用。
上面已经在react中入口文件中注入了react,接下创建一个组件来对redux进行简单的使用
新建 ReduxTest 组件
import React, { Component, Fragment } from "react"; import stroe from "./stroe"; import { getUserInfoAction} from "./stroe/action-creators"; class TestRedux extends Component { constructor(props) { super(props) this.handleUpdateUserInfoClick = this.handleUpdateUserInfoClick.bind(this) } handleUpdateUserInfoClick() {
// 创建一个action,然后reducer会进行对于的处理,然后返回一个新的 state const action = getUserInfoAction({name: 'del lee'}) stroe.dispatch(action) } render() { return ( <Fragment> <button onClick={this.handleUpdateUserInfoClick}>跳转</button> </Fragment> ) } } export default TestRedux
4. react-redux 在react中的使用
结合上面的内容,我们修改一下ReduxTest组件
import React, { Component, Fragment } from "react"; import stroe from "./stroe"; import { getUserInfoAction} from "./stroe/action-creators"; // 引入 connect import { connect } from "react-redux"; class TestRedux extends Component { constructor(props) { super(props) // this.handleUpdateUserInfoClick = this.handleUpdateUserInfoClick.bind(this) } // handleUpdateUserInfoClick() { // 创建一个action,然后reducer会进行对于的处理,然后返回一个新的 state // const action = getUserInfoAction({name: 'del lee'}) // stroe.dispatch(action) //} render() { return ( <Fragment> <!-- <button onClick={this.handleUpdateUserInfoClick}>跳转</button> --> <button onClick={this.props.handleUpdateUserInfoClick}>跳转</button> </Fragment> ) } } const mapStateToProps = state => ({ userInfo: state.userInfo }) const mapDispatchToProps = (dispatch) => ({ handleUpdateUserInfoClick: ()=> { const action = getUserInfoAction({name: 'del lee'}) dispatch(action) // 执行action } }) // export default TestRedux // 修改为,connect会将 mapStateToProps 与 mapDispatchToProps中的内容链接到 TestRedux 组件的props中 // mapStateToProps 会接受到 state 仓库中所有的值 // mapDispatchToProps: 会接受到 dispatch 方法 export default connect(mapStateToProps, mapDispatchToProps)(TestRedux )
备注:为了确保redux中的state不能够直接修改其中的值和统一数据格式,一般建议结合 immutable.js 使用
具体需查阅官方文档:https://immutable-js.github.io/immutable-js/docs/#/
示例: 修改 reducer.js 文件
import { GET_USERINFO_ACTION } from './action-type' import { fromJS } from "immutable"; // 创建一个默认的仓库,一般提出来会更加清晰 //const defaultState = { // userInfo: {} //} // 转换为 immutable 数据格式 const defaultState = fromJS({ userInfo: {} }) // reducer必须是一个纯函数 const reducer = (state=defaultState, action) => { const { type } = action // 深拷贝,一定不能直接对state进行修改 // const newState = JSON.parse(JSON.stringify(state)) immutable数据格式不需要进行深拷贝 if (type === GET_USERINFO_ACTION){ // newState.userInfo = action.value 不能直接修改值 // 使用set方法对值进行修改,会返回一个新的immutable对象 state.set('userInfo', action.value) } return state // 若不匹配直接返回原来的state即可 // return newState }
还需要修改 ReduxTest 中 mapStateToProps 的获取方式
...... const mapStateToProps = state => ({
// userInfo: state.userInfo 会抛出异常
// 使用get或者getIn获取state中的值 userInfo: state.get('userInfo') // or // userInfo: state.getIn(['userInfo']) }) ......
5. Redux-Saga中间件
redux-saga中文文档地址:https://redux-saga-in-chinese.js.org/docs/basics/DeclarativeEffects.html
当我们需要执行一些异步操作时,由于action中只能返回一个对象,从而需要借助一些中间件来达到目的,redux-thunk 和 redux-saga是常见的两种中间件。
redux-thunk 主要是使action能够返回一个函数而达到目的,这样导致了action函数变得复杂
redux-saga 可以将异步操作单独分离出来封装到某些模块,这样保证action函数更加干净
redux-saga的引入:
修改 store/index.js 文件
import 'babel-polyfill' // es6解析 import { createStore, compose, applyMiddleware } from 'redux' import reducer from './reducer' // 需要在 store 目录中创建 sagas.js 文件 import testSaga from "./sagas"; import createSagaMiddleware from "redux-saga"; // 创建 redux-saga 中间件 const sagaMiddleware = createSagaMiddleware(); // redux-dev-tools工具配置 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose const store = createStore( reducer, composeEnhancers( applyMiddleware( sagaMiddleware // 引入saga中间件 ) ) ) // 运行saga sagaMiddleware.run(testSaga) export default store
在 store 中新增 sagas.js 文件
import { call, put, takeEvery } from "redux-saga/effects" // 你可以写一个异步的接口或者一个异步的函数 import { getUserInfoApi } from './api' // 可以在create-action.js中新增一个 action: updateUserInfoAction import { updateUserInfoAction } from 'create-action' /** 在create-action.js新增一个action(当然在reducer.js中也要对state就行对应的操作) * export const updateUserInfoAction = (value) => ({ * type: 'UPDATE_USERINFO_ACTION', * value *}) */ // saga 函数接受 action function* getUserInfoSaga(action) { // 声明 effects 函数 call:发起一次请求 call([api, [args]]),args是请求的参数 const res = yield call(getUserInfoApi, action.userId) // 声明 effects 函数 put: 相当于 store中的dispatch put(updateUserInfoAction(res)) } function* testSaga() { // 当action-type被准备dispatch时,执行 getUserInfo // 声明 effects 函数:takeEvery 监听一个action yield takeEvery('GET_USERINFO_ACTION', getUserInfoSaga) } export default testSaga
这样就完成了一个简单的redux-saga的配置和使用,在component中dispatch getUserInfoAction这个action,就会执行 getUserInfoSaga 函数,这样就完成了异步的拓展。
redux-saga中有很多 声明 effects 函数(比如:call、put、takeEvery、all、fock等等),具体请查阅redux-saga文档。
备注:redux-saga函数必须是一个Generator函数
拓展:还可以通过以下代码来将saga进行模块化:
import { all, fork } from 'redux-saga/effects' // 以下saga是我个人项目中使用到的 import headNavigationBarSagas from '@/commponents/HeadNavigationBar/store/sagas' import viewsHomeSagas from '@/views/Home/store/sagas' import viewsDetailSagas from '@/views/Detail/store/sagas' import viewsLoginSagas from '@/views/Login/store/sagas' import backstageArticleManage from '@/views/backstage/ArticleManage/store/sagas' // 整合多个模块的saga export default function * rootSaga () { yield all([ fork(headNavigationBarSagas), fork(viewsHomeSagas), fork(viewsDetailSagas), fork(viewsLoginSagas), fork(backstageArticleManage) ]) }
大致介绍了redux、react-redux的基本用法和redux-saga中间件的使用,若有错误请各路大佬指出加以改正和学习