React 可预测状态容器 redux+saga

本贴最后更新于 1578 天前,其中的信息可能已经斗转星移

Redux 是一个 react 的数据仓库,学过 vuex 的同学可能比较熟悉,使用起来和 vuex 相似,但是配置极为麻烦,异步还需要配合 saga 实现,刚开始学这个东西时有点学蒙,现在整理以下

  • 此论坛发帖记得在代码块写上使用语言,因为没有代码块检测,我看不少人 的代码块都是纯白

安装 cnpm i redux react-redux
先来看一下 redux 在单文件中的使用

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Redux</title>
</head>
<script src="https://cdn.bootcss.com/redux/4.0.4/redux.min.js"></script>
<script type="text/javascript">
      //reducer 是一个箭头函数,传入 state (数据) 和 action(动作)
      //这个函数就是一个可预测状态容器,把可能发生的动作提前设定
	var reducer=(state={idx:0},action)=>{//左边的={idx:0}是默认值
        if(action.type=='add'){
          //纯函数,不能更改 state 的值,而是直接返回新的对象
            return {
                ...state,
                idx:state.idx+1
            }
        }
        //默认返回 state  不 返回 redux 没办法获取并储存 state
        return state
    }   
    //使用刚刚自己设定的容器创建 store
    var store = Redux.createStore(reducer)
    //dispatch 执行动作,传入一个对象
    store.dispatch({type:'add'});
    console.log(store.getState().idx)
</script>
<body>
</body>
</html>

解析:Redux 是使用自己定义的函数创建储存器,这就完成了 redux 这个仓库的创建,没有难度,建议码一遍这个函数的结构,会很清晰
之后使用 getState() 获取,dispatch() 执行 action

redux 在 react 中的使用

jsqReducer.js
export default (state={idx:10},action)=>{
      if(action.type="ADD"){
            return {
                 ...state,
                 idx:state.idx+1
            };
      }
      return state;
}

如果有很多仓库,一个一个引入极为不便于管理,可以用内置函数合并这些函数为一个仓库,之后使用键名访问

合并自己刚刚定义的函数 Reducer

Reducers.js
import {combineReducer} from 'redux'
import jsqReducer from './jsqReducer.js'

export default combineReducer({
    /*万能加一减一,右边是另一个文件暴露的函数*/
    jsqReducer: jsqReducer
})

import {createStore} from 'redux'
创建 Store 仓库,使用了刚刚合并的 Reducers

import Reducers from 'Reducers.js'
const store = createStore(Reducers)

import {Proverd} from 'react-redux'
提供器 包裹 react render 内的标签

//包裹,完整代码在下方
<Provider store={store}>
     <App/>
</Provider>

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'

import Reducers from './reducers/Reducers'
import App from './app'

/*创建 store*/
const store =  createStore(Reducers)
console.log(store.getState().jsqReducer)
ReactDOM.render(
    /*使用 recat-redux 组件包装自己的组件,并传入 store*/
    <Provider store={store}>
        <App/>
    </Provider>
    ,
    document.getElementById('app')
)

代码段不支持 jsx 所以颜色很糊,建议复制

#####非简化装饰器
装饰器用于重包装自己的组件,并把 props 改写传入
import {connect} from 'react-redux'

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Button } from 'antd'//antd 是 ui 组件不用管
class app extends Component {
    render() {
        return (
            <div>
                //直接 props 调用
                <h1>{this.props.idx}</h1>
                <Button onClick={() => {
                    //直接 props 调用
                    this.props.dispatch({ 'type': 'ADD' })
                }}>按我加一</Button>
            </div>
        )
    }
}
//装饰器,可以加装 babel 使用更高级写法
export default connect(
    //传入state
    (state) => ({
        idx: state.jsqReducer.a
    })
    ,
    //传入 dispatch 函数
    (dispatch) => ({ dispatch })
)(app)
//两个括号 connect(函数,函数)(app)

至此已经完成了一个 redux 的封装和使用,如果要使用异步,就要再加装一个 saga 实现

saga 解决异步问题

npm install --save redux-saga
npm install --save-dev @babel/plugin-proposal-decorators

redux-saga 就是 saga 本身,而 babel 翻译器是对装饰器语法的支持 类似 java 的装饰器,在函数上方 书写 @connect 这种以 @ 开头的语法简化上方 redux 丑陋的传值方式
####装饰器的 connect

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Table } from 'antd';

@connect(
    (state) => ({
        page: state.escReducer.page,
    }),
    (dispatch) => ({
        dispatch
    })
)
export default class App extends Component {
....

####Saga 实现和执行顺序
#####Saga 会拦截你的 dispatch 请求,优先执行 reducer 的文件,然后再执行你的中间件 saga 文件内的 takeEvery('被拦截的 type',函数:拦截后执行)

#####rootSaga.js

import {takeEvery,put} from 'redux-saga/effects'
import Axios from 'axios'

export default function * (){
    yield takeEvery('LOADDATA',function *(){
        yield console.log('rootSage的LOADDATA被执行')
        const {results} = yield Axios.get('http://xxxxx/api').then(data=>data.data)
        yield put({'type':'CHANGRESULTS',result})
    })
}

#####index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore,applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import {Provider} from 'react-redux'

import Reducers from './reducers/Reducers'
import App from './app'
import rootSaga from './sagas/rootSaga'

//创建中间件
const saga = createSagaMiddleware()
//创建仓库
const store =  createStore(Reducers,applyMiddleware(saga))
//运行拦截器
saga.run(rootSaga)
ReactDOM.render(
    //使用 recat-redux 组件包装自己的组件,并传入 store
    <Provider store={store}>
        <App/>
    </Provider>
    ,
    document.getElementById('app')
)

#####App.js 你的组件

componentDidMount(){
        //这个函数在 takeEvery 处被拦截了
        this.props.dispatch({'type':'LOADDATA'})

        //正常执行到 reducer
        this.props.dispatch({'type':'CHANGRESULT'})
    }

执行 顺序
#####erpReducer.js

export default (state={
    page:1,
    results:[]
},actions)=>{
    if (actions.type=="CHANGRESULTS"){
        console.log('reducer内的CHANGRESULTS被调用')
        return {
            ...state,
            results:actions.results
        }
    }else if (actions.type=="LOADDATA"){
        console.log('reducer内的 LOADDATA 被调用')
        return {
            ...state,
            results:actions.results
        }
    }
    return state;
}

因为 reducer 的监听会先调用所以一般我们都会让监听的字符串不相同
在 saga 中监听 loaddata 获取到数据后再使用

put({type:'changeResults',result}) //重新发送一条 dispatch 到 reducer

其实 redux 和 saga 都被 dva 废了 但必要的了解少不了,比较很多公司还是要用的

  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
3 操作
Devourgod 在 2019-12-29 11:11:46 更新了该帖
Devourgod 在 2019-12-29 11:10:29 更新了该帖
Devourgod 在 2019-12-28 15:34:02 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...