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 废了 但必要的了解少不了,比较很多公司还是要用的
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于