当Redux 和React 相接合,就是使用Redux进行状态管理,使用React 开发页面UI。相比传统的html, 使用React 开发页面,确实带来了很多好处,组件化,代码复用,但是和Redux 接合时,组件化却也带来了一定的问题,组件层层嵌套,有成千上百个,而store确只有一个,组件中怎么才能获取到store?  页面UI就是显示应用程序状态的,如果获取不到store中的state, 那就没法渲染内容了。还有一个问题,就是如果状态发生了变化,组件怎么做到实时监听,实时显示最新的状态?

  对于第一个问题,React组件中怎么获取到store,你可能想到了, 在整个应用程序的最外层组件中把store 作为props 层层向下传递,对于一个小程序,还可以接受, 但对于一个大型程序呢,不可能成千上百个组件中都写上store 属性吧。还有一个解决方案就是context,  把所有组件包含在一个context中,context 提供store 属性,这样就不用层层传递,且所有的组件都会获取到store.,方案可以一试

  对于第二个问题,组件内部想要实时显示最新的状态,那就要使用store.subscribe() 方法,在其里面注册监听函数,获取最新状态,然后注入到组件中,组件更新的方法,就是调用setState() 方法,那我们的每一个组件都变成了有状态的组件。那store.subsribe() 方法,什么时候注册监听函数,必须组件加载完就要注册,componentDidMounted 里调用store.subscribe().

  根据以上两点分析,尝试写一下代码,看不能能实现Redux和React 的接合,使用create-react-app 创建项目react-redux-demo,然后 cd react-redux-demo && npm i bootstrap redux --save,安装boostrap 和redux。 打开项目,在index.js 中引入boostrap.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css'; // 添加bootstrap 样式 ReactDOM.render(<App />, document.getElementById('root'));

  还是最简单的加减counter 开始,点击add 加1, 点击minus 减1, 点击reset 重置。看一下Redux, 由于Redux 就是action, reducer, store,和React 一点关系都没有,所以完全把创建action,创建stroe的内容写成单独的文件,只暴露出React需要的东西给它调用就好了。React 需要store, 需要action, 因为它要dispatch action来改变状态。简单起见,把redux的有关内容都放到一个文件中,在src目录下新建一个文件redux.js

import {createStore} from 'redux';
// state
const initialState = { counter: }; // action
const add = { type: 'ADD' };
const minus = { type: 'MINUS' };
const reset = { type: 'RESET' }; // reducer
function counter(state = initialState, action) {
switch(action.type) {
case 'ADD':
return {
...state,
counter: state.counter +
}
case 'MINUS':
return {
...state,
counter: state.counter -
}
case 'RESET':
return {
         ...state, 
         counter:
       };
default:
return state;
}
}
// 创建store
const store = createStore(counter); // export 出去store 和 action
export {store, add, minus, reset};

  现在就要写React,创建页面ui, 先不管交互,先把页面三个按钮和状态的显示画出来,在src下创建一个ThreeButton.js,

import React, { Component } from 'react'

export default class ThreeButton extends Component {
render() {
return (
<div style={{textAlign: "center"}}>
<h1 id="counter"></h1>
<button type="button" className="btn btn-primary" style={{marginRight: '10px'}}>Add</button>
<button type="button" className="btn btn-success" style={{marginRight: '10px'}}>Minus</button>
<button type="button" className="btn btn-danger">Reset</button>
</div>
)
}
}

  然后在App.js中引入

import React from 'react';
import ThreeButton from './ThreeButton'; function App() {
return (
<ThreeButton></ThreeButton>
);
} export default App;

   准备实现React和Redux的接合,实现页面的交互。首先就是要把store 注入到React中,使用React 的context api. context使用的最开始,是使用createContext创建一个context,  在src 目录下新建一个storeContext.js

import React from 'react';
const storeContext = React.createContext({store: {}})
export {storeContext};

  storeContext 有一个属性Provider, 它是一个组件,有一个value属性,提供真正的组件共享数据,这里就是Redux 创建的store 了。然后用Provider 把组件包起来,该组件和它的子组件都能够获取到共享数据,那就把App 包起来, 那就在index.js中把Redux的store和storeContext.js 引入

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css'; // 添加bootstrap 样式 import { store } from './redux'; // 引入 store
 import { storeContext } from './storeContext'; // 引入storeContext
// 提供sotre 作为共享数据,App及其子组件都能获取到store
const Provider = <storeContext.Provider value={{store: store}}>
<App />
</storeContext.Provider> ReactDOM.render(Provider, document.getElementById('root'));

  那ThreeButton.js 就可以获取到store, 那具体是怎么获取到store的呢?首先还是引入storeContext, 然后在类中加一个静态属性contextType, 它赋值为storeContext, 然后组件中就可以使用this.context 获取到store 了。

import React, { Component } from 'react';
import { storeContext } from './storeContext'; // 引入storeContext export default class ThreeButton extends Component {
static contextType = storeContext; // 加静态属性contextType, 赋值为storeContext componentDidMount() {
let {store} = this.context; // this.context 获取到store
console.log(store);
} render() {
return (
<div style={{textAlign: "center"}}>
<h1 id="counter"></h1>
<button type="button" className="btn btn-primary" style={{marginRight: '10px'}}>Add</button>
<button type="button" className="btn btn-success" style={{marginRight: '10px'}}>Minus</button>
<button type="button" className="btn btn-danger">Reset</button>
</div>
)
}
}

  可以看到控制台打印出了store. 组件终于获取到store, 那就要从store中获取state,注入组件中,那组件就要声明一个状态 allState 来接收store中的state, 同时在componentDidMounted 的时候,调用setState 给它赋值

  static contextType = storeContext; // 加静态属性contextType, 赋值为storeContext

    state = {
allState: {}
} componentDidMount() {
let {store} = this.context; // this.context 获取到store
this.setState({
allState: store.getState()
})
}

  把h1 中0 改为从状态获取

 <h1 id="counter">{this.state.allState.counter}</h1>

  页面中显示为5,没有问题,表明从store中获取的状态没有问题。那就要给三个按钮添加click 事件了,dispatch action 来改变状态,那就添加三个函数。首先从redux.js中引入三个action , 然后声明三个函数dipatch action, 最后就是给按钮添加上click 事件。

import { add, minus, reset } from './redux';
..... add = () => {
let {store} = this.context;
store.dispatch(add);
} minus = () => {
let {store} = this.context;
store.dispatch(minus);
} reset = () => {
let {store} = this.context;
store.dispatch(reset);
} ...
<button type="button" className="btn btn-primary" style={{marginRight: '10px'}}
onClick={this.add}>Add</button>
<button type="button" className="btn btn-success" style={{marginRight: '10px'}}
onClick={this.minus}>Minus</button>
<button type="button" className="btn btn-danger"
onClick={this.reset}>Reset</button>

  点击了按钮,页面的状态并没有刷新,那就是没有subscribe 监听状态的改变, 还是在componentDidMounted 页面里面调用store.subscribe,它的回调函数也很简单,就是获取状态,调用setState()

componentDidMount() {
let {store} = this.context; // this.context 获取到store
this.setState({
allState: store.getState()
}) store.subscribe(() => {
this.setState({
allState: store.getState()
})
})
}

  至此,react 和redux 算是接合成功了。整个threeButton.js 如下

import React, { Component } from 'react';
import { storeContext } from './storeContext'; // 引入storeContext
import { add, minus, reset } from './redux'; export default class ThreeButton extends Component {
static contextType = storeContext; // 加静态属性contextType, 赋值为storeContext state = {
allState: {}
} componentDidMount() {
let {store} = this.context; // this.context 获取到store
this.setState({
allState: store.getState()
}) store.subscribe(() => {
this.setState({
allState: store.getState()
})
})
} add = () => {
let {store} = this.context;
store.dispatch(add);
} minus = () => {
let {store} = this.context;
store.dispatch(minus);
} reset = () => {
let {store} = this.context;
store.dispatch(reset);
}
render() {
return (
<div style={{textAlign: "center"}}>
<h1 id="counter">{this.state.allState.counter}</h1>
<button type="button" className="btn btn-primary" style={{marginRight: '10px'}}
onClick={this.add}>Add</button>
<button type="button" className="btn btn-success" style={{marginRight: '10px'}}
onClick={this.minus}>Minus</button>
<button type="button" className="btn btn-danger"
onClick={this.reset}>Reset</button>
</div>
)
}
}

  现在回想一下组件中获取store, dipatch aciton ,和实现实时监听的步骤, 你会发现当我们再创建另外一个组件的时候,它也有好多相同的步骤 ,

  1 ,添加静态属性contextType,使我们整个组件都能够获取到store,  肯定相同

  2, 添加state来接受store中的state, 肯定相同。

  3,componentDidMounted 下获取state ,监听state, 肯定相同

  4, dispatch action, 这个几乎不相同,因为每一个组件触发的action 不同

  5,render state, 就是页面的ui, 这个也几乎不同。

  我们可以把这个组件分为三个部分,相同的部分不动,那不同的部分要怎么处理?对于不同的部分,通常都是使用函数,不同的部分通过传参的形式传递进来,那就要写一个函数,返回这个组件。由于action 和ui 是两个不同类型的东西,可以分为两种不同的参数,那这个函数接受action 返加一个函数,返加函数再接受一个ui 组件,再返回一个组件,这个组件包含相同的部分。相当于

function connect(action) {
return function(Componnet) {
return class extends React.componnet {
// 相同的部分
render() {
return <Componnet {...this.state}></Componnet>
}
}
}
}

  只要把这个函数封装起来,以后直接调用这个函数,就实现了组件自动获取到store, 自动监听变化,我们只要写ui 和action,然后传递进去就可以了。这个函数其实第三方组件已经封装好了,那就是react-redux 库,它提供了一个connect方法, 看一下它的api, 最常用的就是下面的方式

connect(mapStateToProps, mapDispatchToProps)(MyComponent)。

  和我们自己写的connect 函数使用方法一致, 只不过它的第一个函数可以接受更多的参数,mapStateToProps, 把state 转化成props,因为connect 返回的组件中能够获取到store中的state, 它要把state 传递给myComponnet, 因为myCompoent 才是负责渲染ui, 对于myComponnet 来说,它就是props. 同理也适用于mapDispatchToProps, 在connect的组件它是能获取到store中的dispatch,  当传递给myComponent的时候,它就变成了Props.  再看一下这两个参数怎么使用,首先它们是函数,然后返回对象。 为什么要这样设计呢?只有函数,才能调用,才能通过参数把state和disptch  进行注入,返回对象,便于对象的合并,把所有对象进行合并,形成props 传递给myComponnet.

  对于mapStateToProps 来说,它接受一个state作为参数,返回一个对象,这个对象中的属性就可以在myComponet中使用props 进行获取并使用,值呢?就是 参数state中的属性,myComponent 组件要用到state中的哪个属性,就读取state中的哪个属性作为参数。

const mapStateToProps = state => ({
counter: state.counter
})

  mapDispatchToProps,则相对麻烦一点,它接受一个dispatch 作为参数,返回的对象中属性也是可以在myComponet中使用props 进行获取并使用,值呢,是一个函数,参数可以接受也可以不接受,函数体则是dispatch action

const mapDispatchToProps = dispatch => ({
add: () => dispatch({type: 'ADD'})
})

  React-Redux 除了connect 函数外,还提供了Provider 组件,和我们自定义的storeContext.Provider 一致,不过它的使用方式是直接提供属性,组件身上的属性都能被子组件获取到。npm i react-redux --save 使用react-redux 重写组件。

  首先把storeContext.js 文件去掉,然后在index.js中从React-Redux引入Provider 组件,包含App

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css'; // 添加bootstrap 样式 import { store } from './redux'; // 引入 store
import { Provider } from 'react-redux'; // 提供sotre 作为共享数据,App及其子组件都能获取到store
const ProviderWrapper = <Provider store={store}>
<App />
</Provider> ReactDOM.render(ProviderWrapper, document.getElementById('root'));

  然后ThreeButton.js 就要分为两部分了,一个部分是connect 函数中的第一部分connect(mapStateToProps, mapDispatchToProps), 主要的作用就是把store 获取,转化为props.

另一部分是connect的第二部分myComponent,  它呢,就是接受到props, 渲染组件。 在一个文件中也可以,分两个文件也没有问题。我们就在一个文件中写了,

import React from 'react';
import { add, minus, reset } from './redux';
 import { connect } from 'react-redux';
// 纯渲染组件
function ThreeButton(props) { return (
<div style={{textAlign: "center"}}>
<h1 id="counter">{props.counter}</h1>
<button type="button" className="btn btn-primary" style={{marginRight: '10px'}}
onClick={props.add}>Add</button>
<button type="button" className="btn btn-success" style={{marginRight: '10px'}}
onClick={props.minus}>Minus</button>
<button type="button" className="btn btn-danger"
onClick={props.reset}>Reset</button>
</div>
)
} // 把store中的state 转化为纯渲染组件props
const mapStateToProps = state => ({
counter: state.counter
}) // 获取store中的dispatch,同时和action接合,组成纯渲染组件props,渲染组件中,直接调用对象的属性,就可以dispatch action 了
const mapDispatchToProps = dispatch => ({
add: () => dispatch(add),
minus: () => dispatch(minus),
reset: () => dispatch(reset)
}) // connect 函数把它们接合起来,ThreeButton就可以使用props来使用mapStateToProps和mapDispatchToProps中返回的对象属性
// 同时返回一个组件,可以在父组件App.js 中直接调用
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ThreeButton)

Redux 和React 结合的更多相关文章

  1. redux在react项目中的应用

    今天想跟大家分享一下redux在react项目中的简单使用 1 1.redux使用相关的安装 yarn add redux yarn add react-redux(连接react和redux) 2. ...

  2. 使用Redux管理React数据流要点浅析

    在图中,使用Redux管理React数据流的过程如图所示,Store作为唯一的state树,管理所有组件的state.组件所有的行为通过Actions来触发,然后Action更新Store中的stat ...

  3. [Redux] Adding React Router to the Project

    We will learn how to add React Router to a Redux project and make it render our root component. Inst ...

  4. Redux和React

    export app class Compo1 extends Component{ } Compo1.propType = { a:PropTypes.string, fn:PropTypes.fu ...

  5. Redux 管理React Native数据

    现在让我们看看大致的流程: React 可以触发 Action,比如按钮点击按钮. Action 是对象,包含一个类型以及相关的数据,通过 Store 的 dispatch() 函数发送到 Store ...

  6. redux【react】

    首先介绍一下redux就是Flux的一种进阶实现.它是一个应用数据流框架,主要作用应用状态的管理 一.设计思想: (1).web应用就是一个状态机,视图和状态一一对应 (2).所有的状态保存在一个对象 ...

  7. react+redux教程(八)连接数据库的redux程序

    前面所有的教程都是解读官方的示例代码,是时候我们自己写个连接数据库的redux程序了! 例子 这个例子代码,是我自己写的程序,一个非常简单的todo,但是包含了redux插件的用法,中间件的用法,连接 ...

  8. 实例讲解react+react-router+redux

    前言 总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应 ...

  9. react+redux教程(二)redux的单一状态树完全替代了react的状态机?

    上篇react+redux教程,我们讲解了官方计数器的代码实现,react+redux教程(一).我们发现我们没有用到react组件本身的state,而是通过props来导入数据和操作的. 我们知道r ...

随机推荐

  1. 12 opencv图像合成

    #include < stdio.h > #include < opencv2\opencv.hpp > #include < opencv2\stitching.hpp ...

  2. 一个小问题 关于 com.mysql.jdbc.PacketTooBigException: Packet for query is too large

    这个错本身就是应为传输的数据大于mysql的max_allowed_packet参数默认值造成的: 之前遇到这个问题,一直是改max_allowed_packet的值 ,做项目遇到这个错误改了好几次, ...

  3. IDEA中各种图标

    前言 在用这个开发工具之前对大量的图标先有所了解,会提高不少效率 首先讲下基本的图标     Java类 Java抽象类 Groovy类 注解类 枚举类 异常类 最终的类 接口 包含有main方法的可 ...

  4. Nacos

    欢迎来到 Nacos 的世界! Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您 ...

  5. three.js 居中-组

    原文:https://blog.csdn.net/qq_30100043/article/details/78921224 代码: <!DOCTYPE html> <html lan ...

  6. 【神奇性质】【P5523】D [yLOI2019] 珍珠

    D [yLOI2019] 珍珠 Description 给定一个 deque,要求支持 push_back 和 push_front 操作,并且查询前缀与非和以及后缀与非和. deque中只会有 \( ...

  7. springcloud的Hystrix turbine断路器聚合监控实现(基于springboot2.02版本)

    本文基于方志朋先生的博客实现:https://blog.csdn.net/forezp/article/details/70233227 一.准本工作 1.工具:Idea,JDK1.8,Maven3. ...

  8. 在 Debian 上的 SQL Server 的安裝指引

    我想在 linux 环境下尝试一下 Microsoft SQL Server,但是微软只发布了针对 Red Hat,SUSE,Ubuntu 和 Docker 引擎的.我平时习惯使用 Debian, U ...

  9. CF1208F Bits And Pieces

    CF1208F Bits And Pieces 传送门 思路 这里要运用SOS-DP的思路(\(\text{Sum over Subsets}\)).我在另外一篇博客里介绍过,如有需要可以搜索一下我的 ...

  10. 缓存穿透 & 缓存击穿 & 缓存雪崩

    参考文档: 缓存穿透和缓存失效的预防和解决:https://blog.csdn.net/qq_16681169/article/details/75138876 缓存穿透 缓存穿透是指查询一个一定不存 ...