从零实现一个redux

29a2dedb3a4347a4bd3c469d8ed703e8.png

背景

在Redux出现之前,在构建复杂任务时管理状态是相当痛苦的事情。受Flux应用程序设计模式的启发,Redux设计用于管理Java应用程序中的数据状态。虽然它主要用于React,但是可以使用Redux与不同的框架和库(如jQuery,Angular或Vue)。

Redux确保您的应用程序的每个组件都可以直接访问应用程序的状态,而不必将props发送到子组件,或使用回调函数将数据发送回父组件。

在这篇文章中,我将和大家讨论Redux,它如何根植于函数式编程的概念以及如何决定在应用程序中是否需要它。

什么是Redux

官方版: Redux是一个流行的JavaScript框架,为应用程序提供一个可预测的状态容器。Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动.如下图:

2055834352-59daefda643b8_articlex.png

为什么我们需要Redux?

组件没有它们的状态吗?为什么你需要一个工具来帮助你管理这个状态呢?不要误会我的意思;React是一个比较出色的框架。可以用一个框架来编写一个完整的应用程序。但是随着应用程序变得越来越复杂,越来越多的组件使用一个框架来管理这个可能变得非常棘手。

这是Redux为你节省一天的地方;它减轻了这种应用中出现的复杂性。如果你有一些React的经验,你会知道React的数据流是这样的,父组件传递道具到子组件。在一个庞大的应用程序中,数据通过状态和props流经很多组件,通信往往容易出错。相信我,你的代码将变得难以阅读甚至很难改进。

再看看下面的图表,你就知道我在说什么:


58935ffe91dc40339f62637d44bb7a1c.png

在React(以及其他框架)中,不鼓励两个不具有父子级关系的组件之间的通信。 React建议,如果你必须这样做,你可以按照Flux的模式建立你的全局事件系统 - 这就是需要Redux的地方。

借助Redux,您可以在store中保留所有应用程序状态。如果组件A中发生状态更改,则会将其中继成到store,并且需要注意组件A中状态更改的其他组件B和C可以读取store数据:


427f49cb54da426584b0bac5cd8af492.png

看到了吧?这比我们想象的要好得多。如果我们让组件相互通信,我们会创建一个容易出错和不可读的代码库,但有Redux加入就不同了。组件A将其状态更改发送到store,如果组件B和C需要此状态更改,则可以从store中获取它。因此,我们的数据流逻辑是无缝的。

开始前你需要知道的概念

store

store是一个数据仓库,一个应用中store是唯一的,它里面封装了state状态,当用户想访问state的时候,只能通过store.getState来取得state对象,而取得的对象是一个store的快照,这样就把store对象保护起来。

import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga.js'; 
const sagaMiddleware = createSagaMiddleware();
const store = createStore(todos, applyMiddleware(logger, sagaMiddleware));
sagaMiddleware.run(mySaga);

function App() {
  return (
    <Provider store = {store}>
      <div className="App">
      </div>
    </Provider>
  );
}

export default App;

Actions

action描述了一个更新state的动作,它是一个对象,其中type属性是必须有的,它指定了某动作和要修改的值。让我们看看工作中最基本的一个action例子:


f618b2a76ee4475aa5c0af2fa6522953.png

Reducer

reducer是更新state的核心,它里面封装了更新state的逻辑,reducer由外界提供(封装业务逻辑,在createStore时传入),并传入旧state对象和action,将新值更新到旧的state对象上返回。例如:

const todos = (state = newstate, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            newstate.num = newstate.num + 1;
            return Object.assign({}, state, {
                num: newstate.num
            });
        case 'REDUCE_TODO':
            if (newstate.num > 0) {
                newstate.num = newstate.num - 1;
            }
            return Object.assign({}, state, {
                num: newstate.num
            });
       
        default:
            return state;
    }
}

在构建更复杂的应用程序时,建议使用Redux的combineReducers()方法。此方法将应用程序中的所有reducer合并到一个reducer列表中,其中每个reducer处理其应用程序状态的一部分,每个reducer对应一个与其同名的state对象。在开发大型过程中每个页面应该有自己的reducer。将state进行分割。避免不分割导致state过大引起的页面性能问题。combineReducers使用如下:


1ac7338e7ee243579e2852639d7edae3.png

dispatch

dispatch是一个方法,它用于派发一个动作action,这是唯一的一个能够修改state的方法,它内部会调用reducer来调用不同的逻辑基于旧的state来更新出一个新的state。

有了这些基础,开始我们的第一个简易react-redux购物车

react和redux的安装

1.使用react脚手架create-react-app创建一个react项目

npm install -g create-react-app

项目目录结构如下:


1562319487(1).jpg

2.安装redux和react-redux

npm install --save react-redux
npm install --save redux

3.在React中使用Redux
(1)首先构造界面,引入子组件,react,redux,createstore,provider
在根组件中app.js中引入store对象,它是所有组件的容器,因此它要做所有组件的state提供者的角色,所以它的任务要把state提供给所有子组件使用,这就需要react-redux包提供的一个组件:Provider:Provider也是一个组件,它只有一个属性:store,传入创建好的store对象即可.代码如下:

import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga.js'; 
import todos from './reducer/reducer';
import Con1 from './components/con1/con1';
import Con2 from './components/con2/con2';
import Con3 from './components/con3/con3';
import SagaTest from './components/saga/saga'
import './App.css';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(todos, applyMiddleware(logger, sagaMiddleware));
sagaMiddleware.run(mySaga);
function App() {
  return (
    <Provider store = {store}>
      <div className="App">
        <Con3/>
        <Con1/>
        <Con2/>
        <SagaTest/>
      </div>
    </Provider>
  );
}
export default App;

这样就意味着Provider包裹的所有组件都可合法的取到store。
现在数据已经提供,还需要子组件来接收,同样接收store数据react-redux包也为我们提供了一个方法:connect
connect这个方法非常奇妙,它的功能非常强大,它可以把仓库中state数据注入到组件的属性(this.props)中,这样子组件就可以通过属性的方式拿到仓库中的数据。下面使用connect方法将state数据注入到子组件con1中:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../../action/action';
import './con1.scss';
class Con1 extends Component {
    constructor (props) {
        super(props);
        this.handleClickAdd = this.handleClickAdd.bind(this);
    }
    handleClickAdd() {
      this.props.dispatch(addTodo());
    }
    render () {
        return (
            <div className="con1" onClick={this.handleClickAdd}>
              +
            </div>
        )
    }
}
const mapStateToProps = (state, ownProps) => ({
  state
});
const mapDispatchToProps = (dispatch, ownProps) => ({
  dispatch
});
const Con11= connect(
  mapStateToProps,
  mapDispatchToProps
)(Con1);
export default Con11;

可以看到它的写法很怪,connect是一个高阶函数(函数返回函数),它的最终返回值是一个组件,这个组件(con1)最终“连接”好了顶层组件Provider提供的store数据。
connect的第一个参数是一个函数,它的返回是一个对象,返回的对象会绑定到目标组件的属性上,函数参数state就是store.getState的返回值,使用它就可以取到所有state上的数据。mapStateToProps有两个参数,参数可以为空。当有参数时,组建订阅参数,参数改变将导致组建重新渲染。ownprops是这个组建从父组件接受到的数据。高阶函数传入的参数就是要注入的组件,这里是con1,这样在con1组件中就可以通过this.props.state.num取到待办事项的数据。
这样就可以编写好我们的第一个统计功能(点击按钮num加1),点击按钮触发事件。通过dispatch函数将action传给reducer。reducer收到action后对旧的state做相应的处理生成新的state.action定义了要完成何种操作,比如加减。reducer是一个函数,根据dispatch给他的action的type去判断要怎么样处理state.返回新的state.action和reducer文件的代码如下:

//加1
const addTodo = () => ({
    type: 'ADD_TODO'
});
//减1
const reduceTodo = () => ({
    type: 'REDUCE_TODO'
});

});
export {
    addTodo,
    reduceTodo
}
//reducer
let newstate = {
    num: 0,
    data: {}
};
const todos = (state = newstate, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            newstate.num = newstate.num + 1;
            return Object.assign({}, state, {
                num: newstate.num
            });
        case 'REDUCE_TODO':
            if (newstate.num > 0) {
                newstate.num = newstate.num - 1;
            }
            return Object.assign({}, state, {
                num: newstate.num
            });
        default:
            return state;
    }
}

项目源码:https://github.com/zhou111222/car

至此,一个简易的购物车就完成了,截图如下:

1562323233(1).jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355