React 试图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了我们自己。而 redux 就可以来帮我管理这些。
负责管理数据和业务逻辑,不负责 UI 的呈现;
带有内部状态(内部有this.state,即可以改变状态);
使用 Redux 的 API;
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
很多时候组件不是单单独立存在的,数据和其他公有的"ID"一些异步请求等等的一些属性状态,
既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面 包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
react-redux是一个轻量级的封装库,核心方法只有两个:
// 从React-Redux的调用方法和触发机制着手
// React-Redux原本的写法如下
//1.通过Provider组件将store导入到App组件及内部所有子组件上.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import {
BrowserRouter } from 'react-router-dom'
import './components/App.css'
//1.通过Provider组件将store导入到App组件及内部所有子组件上.
import {
Provider } from 'react-redux';
//此时的store就相当于我们所用到的状态集合
import {
store} from './store/store'
ReactDOM.render(<Provider store={
store}>
<BrowserRouter> //这里是H5路由(history API)这里不做过多解释了
<App /> //被包裹的组件
</BrowserRouter>
</Provider> , document.getElementById('root'));
调用如下:
//导入connect方法,用于从redux的store中导入数据
import {
connect} from 'react-redux'
class Home extends Component {
render(){
//此处省略触发action和state数据展示部分
}
}
export default connect(
(state) => {
//负责注入数据
return {
list: state //list会注入到Home组件的this.props上
}
},
(dispatch) => {
//负责注入方法,collect,cancel_collect会出现在this.props上
return {
collect(item) {
//负责 触发 收藏action
dispatch( collect_action(item) )
},
cancel_collect(id) {
//负责 触发 取消收藏action
dispatch( cancel_collect_action(id) )
}
}
}
)(Home)
大家有没有看到这里的Provider很像上一节提到的组件跨级传参里面的Context.Proveder呢!
对的你没有看错,其实React-Redux也是利用了React官方提供的组件跨级传参加上柯里化高阶组件实现的,
具体怎么实现的,我们来具体分析一下:
我们进行一个Provier的组件封装并按照Redux原先的方式进行调用
下面是具体代码实现:
修改之前的Provider之后会有修改,请耐心看完做后的修改过程的注释,希望你能深刻的理解
import React, {
Component } from 'react'
import Context from "../utils/context"
export default class Provider extends Component {
constructor(props){
super(props);
this.state = {
...this.props.store
}
}
render() {
let {
children} = this.props
return (
<div>
<Context.Provider value={
{
state:{
...this.state}}}>
//这里的value不用我多说,上一节的组件跨级传值说过了
//这里的children也就是传统的组件内部传值用this.props.children接收传过来的东西
//(可以包括组件)相当于 this.props.children就是这里的App组件展示
{
children}
</Context.Provider>
</div>
)
}
}
在src文件夹下的index中调用(主入口),,也就是把Provider组件当作最终的根级组件,把之前的跟组件给包裹起来。
这里是引用我们自己封装的Provider数据入口
import React from 'react';
import ReactDOM from 'react-dom';
import App from './views/NewHoc/index';
import Provider from "./components/provider"
import store from "./store/data"
import "./index.css"
ReactDOM.render(
<Provider store = {
store}>//这里的store就相当与要穿过来的数据数据模拟在下面的代码中,自己也可以尝试模拟更加复杂的是数据
<App /> //上面谈到了App组件的传值和Provider做为最大的根组件进行包裹
</Provider>
,
document.getElementById('root')
);
在这里我只是做了一个简单的模拟redux返回会来的数据
store.js
const Reducers = {
sonReducer : {
name:"儿子",
height:1
},
childOneReducer : {
title:"孙子兵法1",
num:2
},
childTwoReducer : {
type:"孙子兵法2",
left:3
},
}
export default Reducers
这里我们写完了Provider的入口那么接下来就得有出口,那么出口又该怎么写,怎么理解呢?
出口还是结合我们的Consumer进行接收
封装context自然不必多说,直接撸代码
import React from "react"
const Context = React.createContext();
export default Context
views/NewHoc/index,js
import React, {
Component } from 'react'
import Son from "./Son"
import Context from "./context"
export default class Index extends Component {
render() {
let {
sonReducer:
{
name}
} = this.props
return (
<div>
<p className="parent">
我是父组件
</p>
<div>
<Context.Consumer>
{
({
state})=>{
return (
<div>{
num}</div>
)
}
}
</Context.Consumer>
</div>
<Son />
</div>
)
}
}
views/NewHoc/Son,js
import React, {
Component } from 'react'
import ChildOne from './ChildOne'
import ChildTwo from './ChildTwo'
export default class Son extends Component {
render() {
return (
<div>
<p className="son">
我是子组件
</p>
<ChildOne />
<ChildTwo/>
</div>
)
}
}
views/NewHoc/ChildOne,js
import React, {
Component } from 'react'
import Context from "./context"
export default class ChildOne extends Component {
state={
count:999
}
render() {
let {
childOneReducer:{
title,num}
} = this.props
return (
<div>
<p className="childOne">我是孙组件1</p>
<div>
{
title}
</div>
<Context.Consumer>
{
({
state})=>{
return (
<div>{
num}</div>
<button onClick={
()=>{
this.props.handleNum(this.state.count)}}>点我</button>
)
}
}
</Context.Consumer>
</div>
)
}
}
ChildTwo参照ChildOne.
大家会看到在NewHoc/index.js和ChildOne.js中的Context.Consumer出口被复用了多次
那我们是不是可以把他封装起来做为统一的出口呢?像是统一的入口Provider一样。
这里就引入了我们的connect组件也可以看作是原Redux中的调用,
我们是不是可以先把Connect的调用方式分析清楚,它其实是利用了一个叫柯里化高阶组件的东西来实现的。
加入我们这样写ChildOne,ChildTwo和Index组件
import React, {
Component } from 'react'
import connect from "../../utils/connect" //这里是我们封装的connect
class ChildOne extends Component {
state={
count:999
}
render() {
let {
title,
num
} = this.props
return (
<div>
<p className="childOne">我是孙组件1</p>
<div>
{
title}
</div>
<div>{
num}</div>
<button onClick={
()=>{
this.props.handleNum(this.state.count)}}>点我</button>
</div>
)
}
}
export default connect(
(store)=>{
//数据注入
return {
...store.childOneReducer
}
},
(dispatch)=>{
return {
handleNum(count){
//点击事件,触发connect中传过来的dispatch也就是changeState修改状态的函数setState
dispatch({
type:"childOneReducer/num", //模仿redux的action的触发方式判断type类型
data:count //要进行修改的新数据
})
}
}
}
)(ChildOne) //柯里化高阶组件的调用方式
那么我们看看connect组件做了一个什么操作呢?
// 柯里化高阶组件connect的具体内容
import React, {
Component} from "react"
import Context from "./context" //1、我们首先把Context组件引进来用作出口的调用,毋庸置疑
const Connect = (cbk_1, cbk_2) => {
//cbk_1和cbk_2作为一个数据的注入的函数,和数据改变的函数传入
return (Com) =>{
//这里的Com也就是传入的组件 也就是上面代码中的(ChildOne)
return class extends Component{
//抛出的进行数据注入和具有课改变数据功能的匿名组件
render(){
return (
<Context.Consumer>
{
({
state, changeState})=>{
let res_1 = cbk_1(state) //数据注入,数据注入,数据注入
let res_2 = cbk_2(changeState) //dispatch 事件触发,dispatch 事件触发,dispatch 事件触发
return (
<Com {
...res_1} {
...res_2} {
...this.props} /> //抛出带有内容的组件
)
}
}
</Context.Consumer>
)
}
}
}
}
export default Connect //最终抛出Connect
上述代码中我强调了三次dispatch 事件触发
那么我们没有写事件触发啊那到底具体在哪儿写呢?又是怎么触发的呢,
我们在之前说了要调用和触发都是在Provider中进行的
那么我们把之前封装的Provider进行一下修改就可以触发我们在Redux中看到的dispatch 的触发了
修改后的Provider
import React, {
Component } from 'react'
import Context from "../utils/context"
export default class Provider extends Component {
constructor(props){
super(props);
this.state = {
...this.props.store
}
}
changeState = ({
type, data}) => {
//和之前相比,加了一个更改state的方法
let [reducer, target] = type.split("/") //这里是进行了数据的切割childOneReducer/num
this.setState({
//调用setState进行状态的修改(React中唯一个可以修改state的那就是setState())
[reducer]:{
...this.state[reducer], //ES6语法不必多说
[target]: data
}
})
}
render() {
let {
children} = this.props
return (
<div>
<Context.Provider value={
{
state:{
...this.state}, changeState:this.changeState}}>
//changeState:this.changeState 这里的changeState就是给我们的cbk_2(changeState)传入的参数,
//再利用了柯里化组件抛出的匿名函数,再原组件中进行的触发
{
children}
</Context.Provider>
</div>
)
}
}
首先,我们必须清楚的认识到react本身是一个专注于view层的框架。
其次,我们必须要明白Redux本身和React并没有之间的关联,它是一个通用Javscript App模块,用做App State的管理。要在React的项目中使用Redux,比较好的方式是借助react-redux这个库来做连接,这里的意思是,并不是没有react-redux,这两个库就不弄一起用了,而是说react-redux提供了一些封装,一种更科学的代码组织方式,让我们更舒服地在React的代码中使用Redux。我们可以通过npm将react-redux直接安装到我们的项目配置里面,在项目里直接就可以用了。
+ Provider这个模块是作为整个App的容器,在你原有的App Container的基础上再包上一层,它的工作简单概括就是接受Redux的store作为props,并将其声明为context的属性之一,将store放在context里,是为了给下面的connect用的。
需要注意的一点就是Provider里面可以包括页面渲染的任何内容,不是必须Router(路由)。
在这里提一下react性能优化方面的问题: react有一个生命周期函数 shouldComponentUpdate,来控制react的更新 假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 ChatThread 组件执行 shouldComponentUpdate,React 可以跳过其他对话的重新渲染步骤
合理的组件拆分,提高代码1、可组合性 2、可重用性 3、可维护性 4、可测试性
还有一个就是16.8以后的出现的Hook组件也是以后开发的一个放向,
新增的React的几生命周期大家可以看看研究研究
这就是我写的React-Redux的实现原理。也和不足,还请评论指教,您的评论就是对我最大的帮助,在这里我也只是把自己学过的写在了这里,希望能帮助更多的朋友和小伙伴们。。。加油
Encourage yourself: maybe today is not easy, tomorrow will be more difficult, but the day after tomorrow will be very beautiful.