文章出处: 拉 勾 大前端 高薪训练营
JavaScript 状态容器,提供可预测化的状态管理
Store: 存储状态的容器,JavaScript 对象
View: 视图,HTML页面
Actions: 对象,描述对状态进行怎样的操作
Reducers: 函数,操作状态并返回新的状态
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reduxtitle>
head>
<body>
<button id="minus">-button>
<span id="count">0span>
<button id="plus">+button>
<script src="./redux.min.js">script>
<script>
// 3. 存储默认状态
const initialState = {
count: 0
}
// 2. 创建 reducer 函数
function reducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1 };
case 'decrement':
return {
count: state.count - 1 };
default:
return state;
}
}
// 1. 创建 store 对象
const store = Redux.createStore(reducer);
// 4. 定义 action
const increment = {
type: 'increment' }
const decrement = {
type: 'decrement' }
// 5. 获取按钮 给按钮添加点击事件
document.getElementById('minus')
.addEventListener('click', function () {
// 6. 获取dispatch 触发 action
store.dispatch(decrement)
})
document.getElementById('plus')
.addEventListener('click', function () {
// 6. 获取dispatch 触发 action
store.dispatch(increment)
})
// 获取store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
console.log(store)
// 获取 store 中存储的状态 state
console.log(store.getState());
// 订阅数据的变化
store.subscribe(() => {
console.log(store.getState())
document.getElementById('count').innerHTML = store.getState().count
});
script>
body>
html>
// 创建 store 对象
const store = Redux.createStore(reducer);
// 2. 创建 reducer 函数
function reducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1 };
case 'decrement':
return {
count: state.count - 1 };
default:
return state;
}
}
// 获取 store 中存储的状态 state
store.getState()
// 订阅数据的变化
store.subscribe(() => {
console.log(store.getState())
});
// 获取dispatch 触发 action
store.dispatch(increment)
在 React 中组件通信的数据流是单向的,顶层组件可以通过 Props 属性向下层组件传递数据,而下层组件不能向上层组件传递数据。要实现下层组件修改数据,需要上层组件传递修改数据的方法到下层组件。等项目越来越大的时候,组件间传递数据变得越来越困难。
使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
npm install redux react-redux
// src/store/index.js
import {
createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
在根组件中使用store
:
import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
import {
Provider } from 'react-redux'
import {
store} from './store'
/**
* react-redux
* Provider
* connect
*/
ReactDOM.render(
// 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
<Provider store={
store}>
<Counter />
</Provider>,
document.getElementById('root')
);
// src/store/reducers/counter.reducer.js
import {
DECREMENT, INCREMENT } from "../count/counter.const";
const initialState = {
count: 0
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + 1 };
case DECREMENT:
return {
count: state.count - 1 };
default:
return state;
}
}
// src/store/count/counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
connect
方法接受两个参数,返回一个高阶组件。
connect
方法的第一个参数是mapStateToProps
方法,将store中的state传递到组件的props
中,mapStateToProps
方法的参数是state
,返回值是一个对象,会传递到组件中,写法如下:
const mapStateToProps = (state) => ({
count: state.count,
a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})
connect
方法的第二个参数是mapDispatchToProps
方法,将store
中的dispatch
传递到组件的props
中,mapDispatchToProps
方法的参数是dispatch
,返回值是一个对象,对象中的方法可以使用dispatch
,这个对象中的方法会传递到组件中,写法如下:
const mapDispatchToProps = (dispatch) => ({
increment () {
dispatch({
type: 'increment'})
},
decrement () {
dispatch({
type: 'decrement' })
}
})
此外,我们还可以通过redux
中的bindActionCreators
来帮我们创建action
函数:
import {
bindActionCreators} from 'redux'
// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
// 解构
...bindActionCreators({
increment () {
return {
type: 'increment'}
},
decrement () {
return {
type: 'decrement'}
}
}, dispatch)
)
或者写成:
const mapDispatchToProps = dispatch => bindActionCreators({
increment () {
return {
type: 'increment'}
},
decrement () {
return {
type: 'decrement'}
}
}, dispatch)
也可以将bindActionCreators
的第一个参数进行抽离:
import * as counterActions from '../store/actions/counter.actions'
const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
// src/store/actions/counter.actions.js
import {
DECREMENT, INCREMENT } from "../count/counter.const"
export const increment = () => ({
type: INCREMENT})
export const decrement = () => ({
type: DECREMENT})
connect
方法接受mapStateToProps
和mapDispatchToProps
,返回一个高阶组件,然后传入Counter
组件进行导出:
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
最终组件代码如下:
// src/components/Counter.js
import React from 'react'
import {
connect} from 'react-redux'
import {
bindActionCreators} from 'redux'
import * as counterActions from '../store/actions/counter.actions'
function Counter ({
count, increment, decrement}) {
return (
<div>
<button onClick={
decrement}>-</button>
<span>{
count}</span>
<button onClick={
increment}>+</button>
</div>
)
}
// 1. connect 会帮助我们去订阅 store,当store中的状态发生了变化后,可以帮我们重新渲染组件
// 2. connect 方法可以让我们获取 store 中的状态,将状态通过组建的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = (state) => ({
count: state.count,
a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
<button onClick={
() => increment(5)}> + 5</button>
reducer
export const increment = payload => ({
type: INCREMENT, payload})
export const decrement = payload => ({
type: DECREMENT, payload})
reducer
根据接受收到的数据进行处理export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + action.payload };
case DECREMENT:
return {
count: state.count - action.payload };
default:
return state;
}
}
store
中的状态越多,reducer
中的switch
分支就会越多,不利于维护,需要拆分reducer
src/index.js
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {
Provider } from 'react-redux'
import {
store} from './store'
ReactDOM.render(
// 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
<Provider store={
store}>
<App />
</Provider>,
document.getElementById('root')
);
src/store/index.js
// src/store/index.js
import {
createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import {
DECREMENT, INCREMENT } from "../const/counter.const";
import {
HIDEMODAL, SHOWMODAL } from "../const/modal.const";
const initialState = {
count: 0,
show: false
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
...state, count: state.count + action.payload };
case DECREMENT:
return {
...state, count: state.count - action.payload };
case SHOWMODAL:
return {
...state, show: true };
case HIDEMODAL:
return {
...state, show: false };
default:
return state;
}
}
src/App.js
// src/App.js
import Modal from './components/Modal'
import Counter from './components/Counter'
function App() {
return (
<div className="App">
<Counter />
<Modal />
</div>
);
}
export default App;
src/components/Modal.js
// src/components/Modal.js
import React from 'react'
import {
connect } from 'react-redux'
import {
bindActionCreators } from 'redux'
import * as modalActions from '../store/actions/modal.actions'
function Modal ({
showStatus, show, hide }) {
const styles = {
display: showStatus ? 'block': 'none',
width: 200,
height: 200,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
margin: 'auto',
backgroundColor: 'skyblue'
}
return (
<div>
<button onClick={
show}>显示</button>
<button onClick={
hide}>隐藏</button>
<div style={
styles}></div>
</div>
)
}
const mapStateToProps = state => ({
showStatus: state.show
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Modal)
src/store/actions/modal.action.js
// src/store/actions/modal.action.js
import {
HIDEMODAL, SHOWMODAL } from "../const/modal.const"
export const show = () => ({
type: SHOWMODAL })
export const hide = () => ({
type: HIDEMODAL })
src/store/const/modal.const.js
// src/store/const/modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'
reducer
使用reducer
提供的工具combineReducers
合并每一个小的reducer
src/store/reducers/root.reducer.js
// src/store/reducers/root.reducer.js
import {
combineReducers} from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'
// { counter: { count: 0 }, modal: { show: false } }
export default combineReducers({
counter: CounterReducer,
modal: ModalReducer
})
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import {
DECREMENT, INCREMENT } from "../const/counter.const";
const initialState = {
count: 0,
}
export default function counterReducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
...state, count: state.count + action.payload };
case DECREMENT:
return {
...state, count: state.count - action.payload };
default:
return state;
}
}
src/store/reducers/modal.reducer.js
// src/store/reducers/modal.reducer.js
import {
HIDEMODAL, SHOWMODAL } from "../const/modal.const";
const initialState = {
show: false
}
export default function modalReducer (state = initialState, action) {
switch (action.type) {
case SHOWMODAL:
return {
...state, show: true };
case HIDEMODAL:
return {
...state, show: false };
default:
return state;
}
}
创建store
时传入的reducer
则来自于我们刚才定义的root.reducer.js
import {
createStore } from 'redux'
import RootReducer from './reducers/root.reducer'
export const store = createStore(RootReducer)
在每个组件中的mapStateToProps
中也要发生相应的改变(state.counter
和state.modal
):
const mapStateToProps = (state) => ({
count: state.counter.count,
})
const mapStateToProps = state => ({
showStatus: state.modal.show
})
中间价允许我们扩展和增强 redux 应用程序
开发中间件的模板
export default store => next => action => {
}
中间件在开发完成以后只有被注册才能在 Redux 的工作流程中生效
src/store/index.js
// src/store/index.js
import {
createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'
createStore(reducer, applyMiddleware(
logger
))
src/store/middleware/logger.js
const logger = store => next => action => {
console.log(store)
console.log(action)
next(action) // 千万别忘了调用 next(action)
}
export default logger
如果注册多个中间件,中间件的执行顺序就是注册顺序,如:
createStore(reducer, applyMiddleware(
logger,
test
))
那么执行顺序就是先执行logger
中间件,再执行test
中间件。
如果中间件中的结尾不调用next(action)
,则整个流程就会卡在此处不会再往后执行了
当前这个中间件函数不关心你想执行什么样的异步操作,只关心你执行的是不是异步操作,
如果你执行的是异步操作,你在触发 action 的时候,给我传递一个函数,如果执行的是同步操作,就传递一个 action 对象,
异步操作代码要写在你传进来的函数中
当这个中间件函数,在调用你传进来的函数时要将 dispatch 方法传递过去
src/store/middleware/thunk.js
// src/store/middleware/thunk.js
import {
DECREMENT, INCREMENT } from "../const/counter.const";
const thunk = ({
dispatch}) => next => action => {
if (typeof action === 'function') {
return action(dispatch) // action 方法内部会发起新的 dispatch
}
next(action)
}
export default thunk
在action文件中定义异步函数action:
src/store/actions/modal.actions.js
// src/store/actions/modal.actions.js
import {
HIDEMODAL, SHOWMODAL } from "../const/modal.const"
export const show = () => ({
type: SHOWMODAL })
export const hide = () => ({
type: HIDEMODAL })
export const show_async = () => dispatch => {
setTimeout(() => {
dispatch(show())
}, 2000);
}
原本使用show
的地方,现在改用show_async
,实现了异步的功能
4.1.1 redux-thunk 下载
npm install redux-thunk
4.1.2 引入 redux-thunk
import thunk from 'redux-thunk';
4.1.3 注册 redux-thunk
import {
applyMiddleware } from 'redux'
createStore(rootReducer, applyMiddleware(thunk));
4.1.4 使用 redux-thunk 中间件
const loadPosts = () => async dispatch => {
const posts = await axios.get('/api/posts').then(response => response.data);
dispatch({
type: LOADPOSTSSUCCE, payload: posts});
}
redux-saga
可以将异步操作从Action Creator
文件中抽离出来,放在一个单独的文件中。
npm install redux-saga
src/store/index.js
// src/store/index.js
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
src/store/index.js
// src/store/index.js
createStore(reducer, applyMiddleware(sagaMiddleware))
src/store/sagas/counter.saga.js
// src/store/sagas/counter.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
increment } from '../actions/counter.actions'
import {
INCREMENT_ASYNC } from '../const/counter.const'
// takeEvery 接收 action
// put 触发 action
function * increment_async_fn (action) {
yield delay(2000) // 此处会暂停2秒钟
yield put(increment(action.payload))
}
export default function * counterSaga () {
// 接收 action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn) // 第二个函数形参会接受一个 action 函数
}
src/store/actions/counter.actions.js
// src/store/actions/counter.actions.js
// 给 saga 使用
export const increment_async = (payload) => ({
type: INCREMENT_ASYNC, payload });
src/store/const/counter.const.js
// src/store/const/counter.const.js
export const INCREMENT_ASYNC = 'increment_async'
src/components/Counter.js
<button onClick={
() => increment_async(20)}>+</button>
src/store/index.js
// src/store/index.js
import counterSaga from './sagas/counter.saga'
sagaMiddleware.run(counterSaga);
src/store/saga/root.saga.js
// src/store/saga/root.saga.js
import {
all } from 'redux-saga/effects'
import counterSaga from './counter.saga'
import modalSaga from './modal.saga'
export default function * rootSaga () {
yield all([
counterSaga(),
modalSaga()
])
}
modal.saga.js 没变,modal.saga.js 如下
src/store/saga/modal.saga.js
// src/store/saga/modal.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
show } from '../actions/modal.actions'
import {
SHOWMODAL_ASYNC } from '../const/modal.const'
// takeEvery 接收 action
// put 触发 action
function * showModal_async_fn () {
yield delay(2000)
yield put(show())
}
export default function * modalSaga () {
// 接收 action
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
store 入口文件中的 saga 中间件启动 root.saga
src/store/index.js
// src/store/index.js
import rootSaga from './sagas/root.saga'
sagaMiddleware.run(rootSaga)
redux 流程中大量的样板代码读写很痛苦,使用 redux-action 可以简化 Action 和 Reducer 的处理
npm install redux-actions
import {
createAction } from 'redux-actions'
const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
src/store/actions/counter.actions.js
src/store/actions/counter.actions.js
// 使用 redux-actions
import {
createAction } from 'redux-actions'
export const increment = createAction('increment')
export const decrement = createAction('decrement')
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import {
handleActions as createReducer } from 'redux-actions'
import {
increment, decrement } from '../actions/counter.actions'
const initialState = {
count: 0,
}
const handleIncrement = (state, action) => ({
count: state.count + action.payload
})
const handleDecrement = (state, action) => ({
count: state.count - action.payload
})
export default createReducer({
[increment]: handleIncrement,
[decrement]: handleDecrement,
}, initialState)
组件使用:
src/components/Counter.js
// src/components/Counter.js
function Counter ({
count, increment, decrement}) {
return (
<div>
<button onClick={
() => decrement(1)}>-</button>
<span>{
count}</span>
<button onClick={
() => increment(1)}>+</button>
</div>
)
}
redux-actions 也可以结合在 redux-saga 中使用