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
  1. The component triggers Action through the dispatch method
  2. Store accepts Action and distributes Action to Reducer
  3. Reducer changes the state according to the Action type and returns the changed state to the Store
  4. 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
  1. Pass parameters

    <button onClick={() => increment(5)}> + 5</button>
  2. Accept parameters, pass reducer

    export const increment = payload => ({type: INCREMENT, payload})
    export const decrement = payload => ({type: DECREMENT, payload})
  3. reducer according to the received data

    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;
      }
    }
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)
  1. The bottom configuration of the ejection project: npm run eject
  2. Download the decorator syntax babel plugin: npm install @babel/plugin-proposal-decorators
  3. Add configuration in the package.json file
"babel": {
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ]
  ]
}

Enable decorator syntax support (Method 2)

  1. npm install react-app-rewired customize-cra @babel/plugin-proposal-decorators
  2. Create config-overrides.js in the project root directory and add configuration

    const { override, addDecoratorsLegacy } = require("customize-cra");
    module.exports = override(addDecoratorsLegacy());
  3. 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.


zhuanglog
162 声望9 粉丝

« 上一篇
React源码解读