directory
- preface
- Initialize the project
- 1. Install scaffolding globally
- 2. Create projects
- 3. Project Catalog
- Implement Redux basic functionality
- 1. The story
- 2. Use it with React
- Implement the React – story
- 1.context
- 2.react-readux
- To realize the Provider
- To realize the connect
- Implement the Redux middleware mechanism
- Implement applyMiddleware
- Implement redux middleware
- Add multiple middleware processes
preface
Redux, as the React state management tool, has become indispensable for large-scale application development. In order to better understand the entire implementation mechanism of Redux, we decided to implement a Redux with basic functions from scratch
Project address welcome star/fork
preview
Initialize the project
1. Install scaffolding globally
npm install -g create-react-appCopy the code
2. Create projects
create-react-app mini-reduxCopy the code
3. Project Catalog
├─ ├─ public │ ├─ favicon │ ├─ └.html │ └ ─ ─ the manifest. Json └ ─ ─ the SRC └ ─ ─ App. CSS └ ─ ─ App. Js └ ─ ─ App. Test, js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker.jsCopy the code
Implement Redux basic functionality
1. The story
Create ~/ SRC /mini-redux/mini-redux.js, redux will expose a createStore method and accept reducer as the reducer parameter
export function createStore(reducer) {
let currentState = {}
let currentListeners = []
function getState() {
return currentState
}
function subscribe(listener) {
currentListeners.push(listener)
}
function dispatch(action) {
currentState = reducer(currentState, action)
currentListeners.forEach(v= > v())
return action
}
dispatch({type: '@REACT_FIRST_ACTION'}) // Initialize state
return { getState, subscribe, dispatch}
}Copy the code
Now that we have implemented the basic functions of Redux, let’s call our implementation of Mini-Redux to check whether it meets expectations. New ~ / SRC/index. Redux. Js
import { createStore } from './mini-redux/mini-redux'
const ADD = 'ADD'
const REMOVE = 'REMOVE'
// reducer
export function counter(state=0, action) {
switch (action.type) {
case ADD:
return state + 1
case REMOVE:
return state - 1
default:
return 10}}export function add() {
return {type: 'ADD'}}export function remove() {
return {type: 'REMOVE'}}const store = createStore(counter)
const init = store.getState()
console.log('Start value:${init}`)
function listener(){
const current = store.getState()
console.log('Current value:${current}`)}// The listener is executed each time state is changed
store.subscribe(listener)
// Submit a request for a status change
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'REMOVE' })
store.dispatch({ type: 'REMOVE' })Copy the code
Import the above file in index.js for execution and view the console to see the following log information
Starting value:10 index.redux.js:27Current value:11 index.redux.js:31Current value:12 index.redux.js:31Current value:11 index.redux.js:31Current value:10 index.redux.js:31Copy the code
So far, we’ve implemented Redux, but it’s far from what we expected because we need to use react with it
2. Use it with React
Use the react and mini-react components together, and modify index.redux.js as follows
const ADD = 'ADD'
const REMOVE = 'REMOVE'
// reducer
export function counter(state=0, action) {
switch (action.type) {
case ADD:
return state + 1
case REMOVE:
return state - 1
default:
return 10}}export function add() {
return {type: 'ADD'}}export function remove() {
return {type: 'REMOVE'}}Copy the code
The index.js file initializes redux
import { createStore } from './mini-redux/mini-redux'
import { counter } from './index.redux'
// Initialize redux
const store = createStore(counter)
function render() {
ReactDOM.render(<App store={store} />, document.getElementById('root')); Subscribe (render); // Subscribe (render)Copy the code
From the app.js file we can call redux
import {add, remove} from './index.redux'
class App extends Component {
render() {
const store = this.props.store
// Get the current value
const num = store.getState()
return (
<div className="App">
<p>The initial value is {num}</p>
<button onClick={()= > store.dispatch(add())}>Add</button>
<button onClick={()= > store.dispatch(remove())}>Remove</button>
</div>); }}export default App;Copy the code
As shown above, we can change the mini-Redux state in the React component
Implement the React – story
We’ve already implemented Redux and can use it in conjunction with React. However, the React link is cumbersome and highly coupled, and would not be used in daily development. We’ll use the React-Redux library to connect to React(check out this blog post if you don’t know about React-Redux). Let’s implement a simple react-Redux
1.context
Before implementing react-redux, we need to know the react context. The implementation of react- Redux uses the context mechanism. Here is an example of how to use context.
New ~ / SRC/mini – story/context. Test. Js
import React from 'react'
import PropTypes from 'prop-types'
// Context is global, declared in the component, and all child elements can be retrieved directly
class Sidebar extends React.Component {
render(){
return (
<div>
<p>Sidebar</p>
<Navbar></Navbar>
</div>)}}class Navbar extends React.Component {
// Restrict type, must
static contextTypes = {
user: PropTypes.string
}
render() {
console.log(this.context)
return (
<div>{this.context.user} Navbar</div>)}}class Page extends React.Component {
// Restrict type, must
static childContextTypes = {
user: PropTypes.string
}
constructor(props){
super(props)
this.state = {user: 'Jack'}
}
getChildContext() {
return this.state
}
render() {
return (
<div>
<p>I am {this. State. User}</p>
<Sidebar/>
</div>)}}export default PageCopy the code
2.react-readux
React-redux has two common components: connect and Provider. Connect is used to fetch data from redux (state and Action). Provider is used to place store in context. Make all child elements available to store. Implement connect and Provider, respectively
To realize the Provider
Create ~/ SRC /mini-redux/mini-react-redux
import React from 'react'
import PropTypes from 'prop-types'
// Put store in context, all child elements can be taken directly to store
export class Provider extends React.Component{
// Restrict the data type
static childContextTypes = {
store: PropTypes.object
}
getChildContext(){
return { store:this.store }
}
constructor(props, context){
super(props, context)
this.store = props.store
}
render(){
// Return all child elements
return this.props.children
}
}Copy the code
To verify that the Provider works as expected, modify the ~/ SRC /index.js file as follows
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from './mini-redux/mini-redux'
import { Provider } from './mini-redux/mini-react-redux'
import { counter } from './index.redux'
const store = createStore(counter)
ReactDOM.render(
(<Provider store={store}><App/></Provider>),
document.getElementById('root'))Copy the code
Finally, we need to change the method of obtaining store data in ~/ SRC/app. js file to use connect. However, since connect is not implemented yet, we will use the connect component of the original React-Redux to verify the Provider implemented above
import React, { Component } from 'react';
import { connect } from 'react-redux'
import {add, remove} from './index.redux'
class App extends Component {
render() {
return (
<div className="App">
<p>The initial value is {this.props. Num}</p>
<button onClick={this.props.add}>Add</button>
<button onClick={this.props.remove}>Remove</button>
</div>
);
}
}
App = connect(state= > ({num: state}), {add, remove})(App)
export default App;Copy the code
Verify that the Provider successfully connects to connect
To realize the connect
We implemented above the Provider, but the connect is still use the original react – redux connect, will come here in ~ / SRC/mini – story/mini – react – redux. Add a connect method js file
import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './mini-redux'
// Connect is responsible for linking components and putting data to redux into component properties
// 1. Accept a component, put some data in state, return a component
// 2. Notify components when data changes
export const connect = (mapStateToProps = state=>state, mapDispatchToProps = {}) = > (WrapComponent) => {
return class ConnectComponent extends React.Component{
static contextTypes = {
store:PropTypes.object
}
constructor(props, context){
super(props, context)
this.state = {
props:{}
}
}
componentDidMount(){
const {store} = this.context
store.subscribe((a)= >this.update())
this.update()
}
update(){
// Put mapStateToProps and mapDispatchToProps in this. Props
const {store} = this.context
const stateProps = mapStateToProps(store.getState())
// The method cannot be given directly because dispatch is required
const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
this.setState({
props: {... this.state.props, ... stateProps, ... dispatchProps } }) } render(){return <WrapComponent {. this.state.props} ></WrapComponent>}}}Copy the code
In the code above, we also need to add a bindActionCreators method to mini-redux.js to wrap the actionCreator method with the Dispatch package, as shown below
. function bindActionCreator(creator, dispatch){return (. args) = >dispatch(creator(... args)) }export function bindActionCreators(creators,dispatch){
let bound = {}
Object.keys(creators).forEach(v= >{
let creator = creators[v]
bound[v] = bindActionCreator(creator, dispatch)
})
return bound
}
......Copy the code
Finally, replace the connect in ~/ SRC/app.js with the connect completed above to complete the test
import { connect } from './mini-redux/mini-react-redux'Copy the code
Implement the Redux middleware mechanism
Implement applyMiddleware
While we typically use Redux to extend redux functionality with a variety of middleware, such as redux-Thunk for asynchronous submission of actions, let’s add a middleware mechanism to our Mini-Redux
Modify the ~/ SRC /mini-redux/mini-redux.js code as follows
export function createStore(reducer, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer)
}
let currentState = {}
let currentListeners = []
function getState() {
return currentState
}
function subscribe(listener) {
currentListeners.push(listener)
}
function dispatch(action) {
currentState = reducer(currentState, action)
currentListeners.forEach(v= > v())
return action
}
// Initialize state
dispatch({type: '@REACT_FIRST_ACTION'})
return { getState, subscribe, dispatch}
}
export function applyMiddleware(middleware) {
return createStore= >(... args) => {conststore = createStore(... args)let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (. args) = >dispatch(... args) } dispatch = middleware(midApi)(store.dispatch)return {
...store,
dispatch
}
}
}
......Copy the code
Now that we have added the middleware mechanism to Mini-Redux, let’s use the middleware for verification. Since we don’t have our own middleware, we now use redux-thunk to implement an asynchronous commit action
Modify the ~ / SRC/index. Js
. import { createStore, applyMiddleware }from './mini-redux/mini-redux'
import thunk from 'redux-thunk'
const store = createStore(counter, applyMiddleware(thunk))
......Copy the code
Modify ~/ SRC /index.redux.js to add an asynchronous method
export function addAsync() {
return dispatch= > {
setTimeout((a)= > {
dispatch(add());
}, 2000);
};
}Copy the code
Finally, we will introduce the asynchronous method in ~/ SRC/app.js, modified as follows
. import React, { Component }from 'react';
import { connect } from './mini-redux/mini-react-redux'
import {add, remove, addAsync} from './index.redux'
class App extends Component {
render() {
return (
<div className="App">
<p>The initial value is {this.props. Num}</p>
<button onClick={this.props.add}>Add</button>
<button onClick={this.props.remove}>Remove</button>
<button onClick={this.props.addAsync}>AddAsync</button>
</div>
);
}
}
App = connect(state= > ({num: state}), {add, remove, addAsync})(App)
export default App;Copy the code
And then you can verify it
Implement redux middleware
We used the Redux-Thunk middleware above, so why not write your own
New ~ / SRC/mini – story/mini – story – thunk. Js
const thunk = ({dispatch, getState}) = > next => action= > {
// If it is a function, execute it with the arguments dispatch and getState
if (typeof action=='function') {
return action(dispatch,getState)
}
// By default, nothing is done,
return next(action)
}
export default thunkCopy the code
Replace thunk in ~/ SRC /index.js with thunk implemented above to see if the program still works correctly
On the basis of the above, we will implement an arrThunk middleware, which provides the function of submitting an action array
New ~ / SRC/mini – story/mini – story – arrayThunk. Js
const arrayThunk = ({dispatch,getState}) = >next= >action= >{
if (Array.isArray(action)) {
return action.forEach(v= >dispatch(v))
}
return next(action)
}
export default arrayThunkCopy the code
Add multiple middleware processes
The middleware mechanism we implemented above allows only one middleware to be added, which is not sufficient for our daily development needs
Modify the ~/ SRC /mini-redux/mini-redux.js file
.// Receive middleware
export function applyMiddleware(. middlewares) {
return createStore= >(... args) => {conststore = createStore(... args)let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (. args) = >dispatch(... args) }const middlewareChain = middlewares.map(middleware= >middleware(midApi)) dispatch = compose(... middlewareChain)(store.dispatch)return {
...store,
dispatch
}
}
}
// compose(fn1,fn2,fn3) ==> fn1(fn2(fn3))
export function compose(. funcs){
if (funcs.length==0) {
return arg= >arg
}
if (funcs.length==1) {
return funcs[0]}return funcs.reduce((ret,item) = >(... args)=>ret(item(... args))) } ......Copy the code
Finally, we will use the two middleware thunk and arrThunk at the same time to see whether the above realization of multi-middleware merge is completed
Modify the ~ / SRC/index. Js
. import arrThunkfrom './mini-redux/mini-redux-arrThunk'
const store = createStore(counter, applyMiddleware(thunk, arrThunk))
...Copy the code
Add an addTwice Action generator in ~/ SRC /index.redux.js
. exportfunction addTwice() {
return [{type: 'ADD'}, {type: 'ADD'}}]...Copy the code
Add an addTwice button in ~/ SRC/app.js to modify the corresponding code
import {add, remove, addAsync, addTwice} from './index.redux'
class App extends Component {
render() {
return (
<div className="App">
<p>now num is {this.props.num}</p>
<button onClick={this.props.add}>Add</button>
<button onClick={this.props.remove}>Remove</button>
<button onClick={this.props.addAsync}>AddAsync</button>
<button onClick={this.props.addTwice}>addTwice</button>
</div>
);
}
}
App = connect(state= > ({num: state}), {add, remove, addAsync, addTwice})(App)Copy the code
And you’re done!