For notes, refer to “React and Redux” by Cheng Mo. Understand the Flux framework before reading
Redux framework
The principle of story
The basic principle of Flux is “one-way data flow”, on which Redux emphasizes three basic principles:
-
Unique data source
Unique data source means that the application’s state data should be stored in only one Store. The state on the Store is a tree object, and each component uses only a portion of the tree object’s data. How to structure the state on the Store is the core problem of Redux application
-
Keep status read-only
Keep the state read-only, that is, you cannot change the state directly. To change the state of the Store, you must distribute an Action object. The way to change the state is not to modify the value of the state, but to create a new state object and return it to Redux, who then completes the assembly of the new state.
-
Data changes can only be done with pure functions
The pure function described here is Reducer, and the first three letters of Redux, Red, represent Reducer. According to creator Dan Abramov, Redux’s name means Reducer+Flux
reducer(state,action) Copy the code
The first parameter state is the current state, the second parameter action is the received action object, and the reducer function needs to produce a new object return based on the state and action values. Note that reducer must be a pure function. That is, the return of the function must be entirely determined by the state and action parameters, without any side effects, and without modifying the state and action objects
function reducer (state,action) = >{ const {counterCaption}=action switch(action.type){ case ActionTypes.INCREMENT: return{... state,[counterCaption]:state[counterCaption]+1} case ActionTypes.DECREMENT: return{... state,[counterCaption]:state[counterCaption]-1} default: return state } } Copy the code
Redux sample
The sample
The basic directories are as follows:
Effect:
The sample source code
The installation
npm i --save redux
Copy the code
Action
Like Flux, Redux applications customarily split the action type and action constructor into two file definitions
SRC/ActionTypes. Js:
export const INCREMENT='increment'
export const DECREMENT='decrement'
Copy the code
SRC/Actions. Js:
import * as ActionTypes from './ActionTypes.js';
export const increament=(counterCaption) = >{
return {
type:ActionTypes.INCREMENT,
counterCaption:counterCaption
}
}
export const decrement=(counterCaption) = >{
return {
type:ActionTypes.DECREMENT,
counterCaption:counterCaption
}
}
Copy the code
Comparison: Each action constructor in Redux returns an Action object. In the Flux version, the Action constructor calls Dispatcher’s Dispatch function directly to dispatch the Action object. The difference between Redux and Flux is that redux is not distributed in an Actions file
/ / flux versions import * as AcitonTypes from './ActionTypes'; import AppDispatcher from './AppDispatcher' export const increment = (counterCaption) = >{ AppDispatcher.dispatch({ type:AcitonTypes.INCREMENT, counterCaption:counterCaption }) } export const decrement = (counterCaption) = >{ AppDispatcher.dispatch({ type:AcitonTypes.DECREMENT, counterCaption:counterCaption }) } Copy the code
Store
The createStore function provided by the Redux library: The first parameter represents the reducer of the update state, the second parameter is the initial value of the state, and the optional third parameter represents StoreEnhancer. The state is obtained by getStore()
src/Store.js
import {createStore} from 'redux'
import reducer from './Reducer.js'
const initValues={
'First':0.'Second':10.'Third':20
}
const store=createStore(reducer,initValues)
export default store
Copy the code
Reducer
Used to update status. This is where any action dispatched through Dispatch changes the store state
src/Reducer.js
import * as ActionTypes from './ActionTypes.js'
export default (state,action)=>{
const {counterCaption}=action
switch(action.type){
case ActionTypes.INCREMENT:
return{... state,[counterCaption]:state[counterCaption]+1}
case ActionTypes.DECREMENT:
return{... state,[counterCaption]:state[counterCaption]-1}
default:
return state
}
}
Copy the code
Redux has one more parameter state than Flux, but there is no parameter state in Flux. The difference between Redux and Flux is that the state in Flux is managed by Store, while the state in Redux is managed by Redux itself.
/ / flux versions CounterStore.dispatchToken=AppDispatcher.register((action) = >{ if(action.type===AcitonTypes.INCREMENT){ counterValues[action.counterCaption]++ CounterStore.emitChange() }else if(action.type===AcitonTypes.DECREMENT){ counterValues[action.counterCaption]-- CounterStore.emitChange() } }) Copy the code
View
src/views/ControlPanel.js
import React from 'react'
import Counter from './Counter'
import Summary from './Summary'
export default class ControlPanel extends React.Component{
render(){
return(
<div>
<Counter caption="First" />
<Counter caption="Second" />
<Counter caption="Third" />
<hr/>
<Summary/>
</div>)}}Copy the code
src/views/Counter.js
import React from 'react'
//1. Introduce store and action
import store from '.. /Store.js'
import * as Actions from '.. /Actions'
export default class Counter extends React.Component{
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
//3. Initialization status
this.state = this.getOwnState()
}
//2. Return a corresponding state obtained from store
getOwnState(){
return {value:store.getState()[this.props.caption]}
}
//4. Keep store state in sync with this.state and call onChange whenever store changes
Subscribe and unsubscribe are used to register and unsubscribe listeners, respectively
componentDidMount(){
store.subscribe(this.onChange)
}
componentWillUnmount(){
store.unsubscribe(this.onChange)
}
onChange(){
this.setState(this.getOwnState())
}
//5. The only way to change the store state is to issue an action. Call the store.dispatch function to dispatch the action
onClickIncrementButton(){
store.dispatch(Actions.increment(this.props.caption))
}
onClickDecrementButton(){
store.dispatch(Actions.decrement(this.props.caption))
}
render(){
const {caption} =this.props
const value=this.state.value
return(
<div>
<input type="button" onClick={this.onClickIncrementButton} value="+"/>
<input type="button" onClick={this.onClickDecrementButton} value="-"/>
<span>{caption} count:{value}</span>
</div>)}}Copy the code
src/views/Summary.js
import React from 'react'
import store from '.. /Store.js'
export default class Summary extends React.Component{
constructor(props){
super(props)
this.onUpdate = this.onUpdate.bind(this);
this.state=this.getOwnState.bind(this);
}
//1. Define a summation function that returns an object
getOwnState(){
const state=store.getState();
let sum=0
for(const key in state){
if(state.hasOwnProperty(key)){
sum+=state[key]
}
}
return {sum:sum}
}
//2. Synchronization status
componentDidMount(){
this.setState(this.getOwnState())
store.subscribe(this.onUpdate)
}
componentWillUnmount(){
store.unsubscribe(this.onUpdate)
}
onUpdate() {
this.setState(this.getOwnState())
}
render(){
return(
<div>
total:{this.state.sum}
</div>)}}Copy the code
conclusion
- Defining action types
- Create the Action constructor
- CreateStore with createStore — reducer, initial state, (StoreEnhancer optional)
- Create reducer: Contains state and ation parameters
- View partial initialization state: get the state from store.getStore()
- Synchronizes states with subscribe and unsubscribe
- Action is dispatched via store.dispatch to change the store state
Improved Redux(1) – Container components and presentation components
In the Redux framework, a React component basically performs two functions:
- Interacts with the Redux Store, reads the state of the Store, initializes the component’s state, and listens for changes in the Store’s state. When Store state changes, component state needs to be updated to drive component re-rendering; When the Store state needs to be updated, the action object should be distributed.
- Render the user interface based on the current props and state
If you find that a component is doing too much, you can split the component into multiple components so that each component still does only one thing:
- The Component responsible for dealing with the Redux Store is the outer layer, called the Container Component.
- The component responsible for rendering the interface, located in the inner layer, is called the Presentation Component.
Now modify the Counter component:
import React from 'react'
import store from '.. /Store.js'
import * as Actions from '.. /Actions'
//1. Container components
class CounterContainer extends React.Component{
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onIncremnet = this.onIncremnet.bind(this);
this.onDecrement = this.onDecrement.bind(this);
this.state = this.getOwnState()
}
getOwnState(){
return {value:store.getState()[this.props.caption]}
}
// Synchronization status
componentDidMount(){store.subscribe(this.onChange)}
componentWillUnmount(){store.unsubscribe(this.onChange)}
onChange(){this.setState(this.getOwnState())}
Distributed / / action
onIncremnet(){
store.dispatch(Actions.increment(this.props.caption))
}
onDecrement(){
store.dispatch(Actions.decrement(this.props.caption))
}
render(){
return <Counter caption={this.props.caption}
onIncremnet={this.onIncremnet}
onDecrement={this.onDecrement}
value={this.state.value}
/>}}// Export container components by default
export default CounterContainer
//2
function Counter(props){
const {caption,onIncremnet,onDecrement,value}=props
return(
<div>
<input type="button" onClick={onIncremnet} value="+"/>
<input type="button" onClick={onDecrement} value="-"/>
<span>{caption} count:{value}</span>
</div>)}//2
// function Counter({caption,onIncremnet,onDecrement,value}){
// return(
//
//
//
// {caption} count:{value}
//
/ /)
// }
Copy the code
The sample source code
Improved Redux(2) – Component Context
In both the Counter and Summary component files, store is imported. However, importing stores directly into a component is very detrimental to component reuse. In an application, there should be only one place to import the Store directly. This should be where the React component is called at the top level. In this case, this location is the entry file SRC/index.js for the application
Instead of importing a component directly into a Store, you have to have the component’s upper layer pass the Store down. However, there is a defect in the props that all components need to help deliver the props. React provides a function called Context that solves this problem perfectly.
src/Provider.js
import React from 'react'
class Provider extends React.Component{
getChildContext(){
return{
store:this.props.store
}
}
render(){
return this.props.children
}
}
Copy the code
Provider provides the function getChildContext, which holds the object representing the Context. In order for a Provider to be recognized as a Context Provider by React, you also need to specify the Provider’s childContextTypes property. The Provider defines the class childContextTypes, which must correspond to getChildContext so that the Provider’s children can access the context.
import PropTypes from 'prop-types';
Provider.childContextTypes={
store:PropTypes.object
}
Copy the code
src/index.js
import store from './Store'
import Provider from './Provider'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>.document.getElementById('root'));Copy the code
src/views/Counter.js
-
Give CounterContainer contextTypes of a class assignment, should work with the Provider. ChildContextTypes values consistent, otherwise unable to access to the context
import PropTypes from 'prop-types'; CounterContainer.contextTypes = { store: PropTypes.object } Copy the code
-
All access to the store is done through this.context.store. (Same goes for SRC /views/ summary.js)
componentDidMount(){this.context.store.subscribe(this.onChange)} Copy the code
-
Since the constructor is defined, the second argument context is used
constructor(props,context) { super(props,context); . }Copy the code
or
constructor() { super(... arguments); . }Copy the code
The sample source code
Improve Redux (3) – the React – story
The first is to split a component into container components and presentation components. The second is to use the React Context to provide a Context that all components can access directly. In fact, there is already a library that does this: React-Redux
npm install --save react-redux
Copy the code
React-redux has two main features:
-
Provider: provides the context containing the store
-
Connect: Connect container components and presentation components;
Provider
In SRC /index.js, react-redux lets you import your own Provider directly from SRC /index.js instead of using it:
import {Provider} from 'react-redux'
Copy the code
connect
Connect eliminates the need to define container components such as CounterContainer and now simply exports a statement like this:
import {connect} from 'react-redux'; .export default connect(mapStateToProps,mapDispatchToProps)(Counter)
Copy the code
Connect accepts two parameters mapStateToProps and mapDispatchToProps. The first execution is the connect function, the second execution is the connect return function (with the display component Counter), and the final result is the container component. The connect function does two things:
-
Convert state on Store to prop for inner presentation components
/ / mapStateToProps function: function mapStateToProps(state,ownProps){ return { value:state[ownProps.caption] //ownProps is a prop object. OwnProps. Caption Reads the Caption property value of a prop object //state[ownProps. Caption] is equivalent to getting the store value of the caption property
Get the First property in the store // Then the presentation component can get the corresponding value via props. Value}}Copy the code -
Convert user actions in the inner presentation component into actions sent to the Store
/ / mapDispatchToProps function function mapDispatchToProps(dispatch,ownProps){ return{ onIncrement:() = >{ dispatch(Actions.increment(ownProps.caption)) }, onDecrement:() = >{ dispatch(Actions.decrement(ownProps.caption)) } } } Copy the code
In contrast to the previous two improvements, the Counter component does not write the synchronization status section
Complete code:
//Counter.js
import * as Actions from '.. /Actions'
import {connect} from 'react-redux';
// Convert state on Store to prop for inner presentation components
function mapStateToProps(state,ownProps){
return {
value:state[ownProps.caption]
}
}
// Convert user actions in the inner presentation component to actions sent to the Store
function mapDispatchToProps(dispatch,ownProps){
return{
onIncrement:() = >{
dispatch(Actions.increment(ownProps.caption))
},
onDecrement:() = >{
dispatch(Actions.decrement(ownProps.caption))
}
}
}
// Export the container component
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
// Display the component
function Counter(props){
const {caption,onIncrement,onDecrement,value}=props
return(
<div>
<input type="button" onClick={onIncrement} value="+"/>
<input type="button" onClick={onDecrement} value="-"/>
<span>{caption} count:{value}</span>
</div>)}Copy the code
//Summary.js
import {connect} from 'react-redux'
// Convert state on Store to prop for inner presentation components
function mapStateToProps(state) {
let sum = 0;
for (const key in state) {
if(state.hasOwnProperty(key)) { sum += state[key]; }}return {value: sum};
}
function Summary({value}) {
return (
<div>Total Count: {value}</div>
);
}
export default connect(mapStateToProps)(Summary);
Copy the code
//index.js
import store from './Store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>.document.getElementById('root'));Copy the code
The sample source code
conclusion
-
Defining action types
-
Create the Action constructor export object
-
CreateStore with createStore — reducer, initial state, (StoreEnhancer optional)
-
Create reducer: Contains state and ation parameters
-
Import the Provider component from the React-redux to provide the context so that the store can be accessed anywhere in the component without importing the store
-
Import the Connect method from react-Redux, accept two functions, a presentation component, and finally generate a container component. One function converts the state on the Store into a prop that displays the component; Another function converts user actions in the presentation component into actions sent to the Store