React state management tool
Redux
1. Redux core
1.1 Introduction to Redux
JavaScript state container, providing predictable state management
1.2 Redux core concepts and processes
Store: container for storing state, JavaScript object
View: View, HTML page
Actions: object, describing how to operate the state
Reducers: function, manipulate state and return new state
1.3 Redux use: counter case
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redux</title>
</head>
<body>
<button id="minus">-</button>
<span id="count">0</span>
<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>
1.4 Redux core API
// 创建 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)
2. React + Redux
2.1 Problems encountered when not using Redux in React
In React, the data flow of component communication is one-way. The top-level component can pass data to the lower-level component through the Props property, while the lower-level component cannot pass the data to the upper-level component. To realize the modification of data by the lower-level component, the upper-level component needs to pass the method of modifying the data to the lower-level component. As the project gets bigger and bigger, it becomes more and more difficult to transfer data between components.
2.2 The benefits of adding Redux to the React project
Using Redux to manage data, because Store is independent of components, data management is independent of components, which solves the problem of difficulty in transferring data between components.
2.3 Download Redux
npm install redux react-redux
2.4 Redux workflow
- The component triggers Action through the dispatch method
- Store accepts Action and distributes Action to Reducer
- Reducer changes the state according to the Action type and returns the changed state to the Store
- The component subscribes to the state in the Store, and the state updates in the Store will be synchronized to the component
2.5 Redux steps
2.5.1 Create store
// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
store
in the root component:
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')
);
2.5.2 Create reducer
// 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'
2.5.3 Use connect in the component to accept the state and dispatch in the store
connect
method accepts two parameters and returns a higher-order component.connect
first argument is mapStateToProps
method, passing in the state to store components props
in mapStateToProps
parameter method is state
, the return value is an object that is passed to the assembly, worded as follows:
const mapStateToProps = (state) => ({
count: state.count,
a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})
connect
second parameter method is mapDispatchToProps
method, store
the dispatch
transferred to the assembly props
in, mapDispatchToProps
parameter method is dispatch
, the return value is an object, the method object can dispatch
, this object the method will Passed to the component, written as follows:
const mapDispatchToProps = (dispatch) => ({
increment () {
dispatch({ type: 'increment'})
},
decrement () {
dispatch({ type: 'decrement' })
}
})
In addition, we can also redux
in bindActionCreators
to help us create action
function:
import {bindActionCreators} from 'redux'
// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
// 解构
...bindActionCreators({
increment () {
return { type: 'increment'}
},
decrement () {
return { type: 'decrement'}
}
}, dispatch)
)
Or written as:
const mapDispatchToProps = dispatch => bindActionCreators({
increment () {
return { type: 'increment'}
},
decrement () {
return { type: 'decrement'}
}
}, dispatch)
You can also bindActionCreators
the first parameter of 0612ba0ffd1876:
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
method accepts mapStateToProps
and mapDispatchToProps
, returns a higher-order component, and then passes in the Counter
component for export:
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
The final component code is as follows:
// 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)
2.5.4 Passing parameters for action
Pass parameters
<button onClick={() => increment(5)}> + 5</button>
Accept parameters, pass
reducer
export const increment = payload => ({type: INCREMENT, payload}) export const decrement = payload => ({type: DECREMENT, payload})
reducer
according to the received dataexport 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; } }
2.6 Redux implementation of pop-up box case
store
more states, reducer
in switch
branch will be more, is not conducive to the maintenance, need to split 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'
2.7 Split reducer
Use the tool reducer
combineReducers
merge each small 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;
}
}
Creating store
passed when reducer
comes from our just defined root.reducer.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'
export const store = createStore(RootReducer)
Corresponding changes will also occur mapStateToProps
in each component state.counter
and state.modal
):
const mapStateToProps = (state) => ({
count: state.counter.count,
})
const mapStateToProps = state => ({
showStatus: state.modal.show
})
3. Redux middleware
3.1 What is middleware
The middle price allows us to extend and enhance the redux application
3.2 Develop Redux middleware
Templates for developing middleware
export default store => next => action => { }
3.3 Registering middleware
After the development is completed, the middleware must be registered before it can take effect in the Redux workflow.
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
If you register multiple middleware, the execution order of the middleware is the registration order, such as:
createStore(reducer, applyMiddleware(
logger,
test
))
Then the execution sequence is to execute the logger
middleware first, and then execute the test
middleware.
next(action)
is not called at the end of the middleware, the entire process will be stuck here and will not be executed in the
3.4 Redux middleware development example thunk (asynchronous middleware)
The current middleware function does not care what kind of asynchronous operation you want to perform, only whether you are performing an asynchronous operation,
If you are performing an asynchronous operation, when you trigger an action, pass me a function, if you are performing a synchronous operation, pass an action object,
Asynchronous operation code should be written in the function you pass in
When this middleware function is called, the dispatch method must be passed when calling the function you passed in.
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
Define the asynchronous function action in the action file:
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
was originally used show_async
is used instead, which realizes the asynchronous function
4. Redux common middleware
4.1 redux-thunk
4.1.1 redux-thunk download
npm install redux-thunk
4.1.2 Introduce redux-thunk
import thunk from 'redux-thunk';
4.1.3 Register redux-thunk
import { applyMiddleware } from 'redux'
createStore(rootReducer, applyMiddleware(thunk));
4.1.4 Use redux-thunk middleware
const loadPosts = () => async dispatch => {
const posts = await axios.get('/api/posts').then(response => response.data);
dispatch({type: LOADPOSTSSUCCE, payload: posts});
}
4.2 redux-saga
4.2.1 Problems solved by redux-saga
redux-saga
can extract asynchronous operations from the Action Creator
file and put it in a separate file.
4.2.2 Download redux-saga
npm install redux-saga
4.2.3 Create redux-saga middleware
src/store/index.js
// src/store/index.js
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
4.2.4 Register sagaMiddleware
src/store/index.js
// src/store/index.js
createStore(reducer, applyMiddleware(sagaMiddleware))
4.2.5 Use saga to accept action to execute operations asynchronously
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>
4.2.6 Start saga
src/store/index.js
// src/store/index.js
import counterSaga from './sagas/counter.saga'
sagaMiddleware.run(counterSaga);
4.2.7 Merging saga
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 has not changed, modal.saga.js is as follows
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)
}
The saga middleware in the store entry file starts root.saga
src/store/index.js
// src/store/index.js
import rootSaga from './sagas/root.saga'
sagaMiddleware.run(rootSaga)
4.3 redux-actions
4.3.1 Problems solved by redux-actions
It is painful to read and write a lot of boilerplate code in the redux process. Using redux-action can simplify the processing of Action and Reducer
4.3.2 redux-action download
npm install redux-actions
4.3.3 Create Action
import { createAction } from 'redux-actions'
const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
4.3.4 Create Reducer
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)
Component usage:
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 can also be used in combination with redux-saga
MobX
1. Introduction to Mobx
1.1 Introduction to Mobx
Simple and extensible state management library
Mobx is sponsored by Mendix (code development platform), Coinbase (Bitcoin company), Facebook open source and many individual sponsors
React and Mobx are a powerful combination. React is responsible for rendering the application state, and Mobx is responsible for managing the application state for React to use
1.2 MobX browser support
MobX 5 version runs on any browser that supports ES6 Proxy, does not support IE11, Node.js 6
MobX 4 can run on any browser that supports ES5
The API of MobX 4 and 5 are the same
2. Preparation before development
2.1 Enable decorator syntax support (Method 1)
- The bottom configuration of the ejection project:
npm run eject
- Download the decorator syntax babel plugin:
npm install @babel/plugin-proposal-decorators
- Add configuration in the package.json file
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
Enable decorator syntax support (Method 2)
npm install react-app-rewired customize-cra @babel/plugin-proposal-decorators
Create config-overrides.js in the project root directory and add configuration
const { override, addDecoratorsLegacy } = require("customize-cra"); module.exports = override(addDecoratorsLegacy());
package.json
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test" }
2.2 Resolve the warning of the vscode editor about the decorator syntax
Press command + comma in vscode, and enter
javascript.implicitProjectConfig.experimentalDecorators
Modify the configuration:"javascript.implicitProjectConfig.experimentalDecorators": true
3. Mobx + React
3.1 Download Mobx
npm install mobx mobx-react
3.2 Mobx workflow
Action -> state -> Views
5. Mobx data monitoring
5.1 computed value
When to use the calculated value
Extract complex business logic from the template
5.2 autorun method
When the monitoring state changes, and you want to produce "effects" based on the state, please use autorun.
Autorun will be executed once during initialization and every time the state changes.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。