Redux概念

React本身是一个轻量级的视图框架,如果只用react框架,做大型项目的时候,组件间的传值将会变得非常麻烦。这个时候,我们需要用引入数据管理框架Redux, Redux就是把组件中的数据,放到一个公用的存储区域去存储,然后当一个组件改变store里的数据的时候,其他组件会感知到store里数据的变化,然后重新去取store里的最新数据。Redux大大简化了数据的传递难度。
image.png
Redux = Reducer + Flux
Redux的工作流程

简单理解为以下流程:组件(React Components)告诉Store,我需要获取你里边的数据(Action Creators),但是到底该给组件什么数据呢?Store并不知道, 这个需要Reducers告诉Store应该给什么数据,Store知道了以后,把数据给组件就可以了。
同样道理,当组件要修改Store里的数据的时候,需要先告诉Store,我要改变你里边的数据(Action Creators),但是Store不知道怎么去帮你改变里边的数据,它需要去问 Reducers,Reducers会告诉Store该怎么去修改数据,当Store改好数据以后,会通知组件,数据已经修改好了,你可以重新来获取数据了。这样组件就可以重新获取最新的数据了!

image.png

redux使用流程
  • 安装redux npm install --save redux
  • 创建一个store,具体方法是,src下,创建一个store文件夹,里边创建一个index.js文件,然后index.js里边引入redux里的 createStore方法,当然我们不能单单创建一个store,还需要在创建store的时候,传入一个reducer,reducer返回的必须是一个函数,这个函数接收两个参数,state和action,reducer负责管理整个应用里的一些数据,怎么处理,怎么存。
    image.png

    image.png

    第一次reducer第一次执行的时候,store传的state是没有数据的,所以准备了一个defaultState作为默认值,复制给state,然后再把这个state返回给store,所以defaultState实际上,就是store的默认值。
具体使用流程,以todolist为例
  • 首先我们要实现input框输入的时候,store里的inputValue值跟着改变的功能。然后把store里的inputValue值放入store的list.
// 第一步:input框上,要监听input的change事件,并绑定事件处理函数。
     <Input 
          value={this.state.inputValue}  
          placeholder="todo info" 
          style={{width:'300px',marginRight:'10px'}}
          onChange={this.handleInputChange}
     >
     </Input>

// 第二步:当change事件触发的时候,创建一个action,action是一个对象。
// action包含type,要做什么事(自定义名字),和要传的数据。
// 然后再调用store的dispatch方法,把这个action传给store
    handleInputChange(e){
        const action = {
            type:'change_input_value',
            value:e.target.value
        }
        store.dispatch(action);
    }

// 第三步:store本身是不对数据进行处理的,接收到组件传的action后,会把当前store里的内容和这个action,一起给到reducer。
// reducer就能接受到store之前的内容(state)和传过来的action
// reducer根据action的类型,对数据进行处理,然后把新的数据return出去,返回给store
export default (state = defaultState, action) => {
    if(action.type === 'change_input_value'){
        // 注意 reducer可以接收state,但是决不能修改state,所以下边要对state进行深拷贝
        const newState = JSON.parse(JSON.stringify(state));
        newState.inputValue = action.value;
        return newState;
    }
    if(action.type === 'add_todo_item'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        newState.inputValue = '';
        console.log(newState);
        return newState;
    }
    return state;
}

// 第四步 store拿到最新的state之后,把之前的state替换掉

// 第五步 组件通过store.subscribe方法监听store的变化,并触发绑定的函数
// 在subscribe绑定过函数里,重新获取store里的最新的数据,并修改自己的state
    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        store.subscribe(this.handleStoreChange);
    }
    handleStoreChange(){
        this.setState(store.getState());
    }

注意 reducer可以接收state,但是决不能修改state,所以要对state进行深拷贝

ActionTypes的拆分
  • 当我们创建action的时候,会定义这个action的type,值是自定义的字符串,但是这里有个问题,当我们使用这个字符串进行action的判断的时候,如果字符串写错了,页面是不会报错的,不利于我们定位问题。这个时候,我们可以对action的type进行拆分,使用常量代替原来的字符串。
// 先在store下创建 actionTypes文件,里边定义types的常量并导出
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';


// 在用到action的type的地方,引入刚才定义的常量
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './store/actionTypes';

// 使用的时候,直接用常量代替字符串
    handleBtnClick(e){
        const action = {
            type:ADD_TODO_ITEM
        }
        store.dispatch(action);
    }
  • 然后我们在使用过程中,如果不小心写错了变量某个字符,页面就会报错。
使用actionCreators 统一创建 action
  • 比较简单功能,action分散在业务逻辑里边还可以,但是当业务逻辑非常复杂的情况下,action到处都是,对后期的测试和管理是非常不方便的。所以,我们要把action拆分到actionCreator 里进行统一的创建管理。
// 在store下创建actionCreators.js文件,我们把action的创建统一放到这个文件里,对action进行统一的管理,
// 主要是为了提高代码的可维护性,方便自动化测试
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes';

// 定义一个常量,是一个函数,该函数返回一个对象,对象包含action的type和值
export  const getInputChangeAction = (value) => ({
    type: CHANGE_INPUT_VALUE,
    value
})

export  const getAddItemAction = () => ({
    type: ADD_TODO_ITEM
})

export  const getDeleteItemAction = (index) => ({
    type: DELETE_TODO_ITEM,
    index
})

// 具体的使用,先引入这些actionCreators 里的action
import {getInputChangeAction, getAddItemAction,getDeleteItemAction} from './store/actionCreators';

// 然后使用store.dispatch去触发action
    handleInputChange(e){
        const action = getInputChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(store.getState());
    }
    handleBtnClick(e){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }

注意Redux的设计使用原则

  • 注意Redux的设计使用原则,第一个就是store必须是唯一的,也就是整个应用中只有一个store作为公用存储
  • 只有store能够改变自己的内容。(reducer返回了数据,store会用这个数据自己去替换自己原来的数据),换句话说这也是为什么reducer不能修改state的内容,state指的就是store里的数据
  • Reducer必须是纯函数(纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何的副作用)。如果函数里有ajax请求,setTimeout等异步的时候或与日期相关的时候,就不是固定的输入了,所以Reducer里不能有异步的操作,也不能有跟时间相关的操作
export default (state = defaultState, action) => {
    if(action.type === CHANGE_INPUT_VALUE){
        const newState = JSON.parse(JSON.stringify(state));
        // 如果inputValue = new Date().那么就不会是一个固定的值了。是不行的。
        newState.inputValue = action.value;
        // 如果把newState 换成state,那么就是改变了参数,而对reducer传入的参数的修改是有副作用的。
        state.inputValue = action.value;
        return newState;
    }
    if(action.type === ADD_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        newState.inputValue = '';
        return newState;
    }
    if(action.type === DELETE_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.index,1);
        return newState;
    }
    
    return state;
}
Redux-DevTools 是一个非常棒的工具,它可以让你实时的监控Redux的状态树的Store,在谷歌扩展程序安装后,要想开发环境下使用,还需配置。
// 未配置之前,创建的store
import { createStore} from "redux";
import reducer from './reducer';

const store = createStore(reducer);
export default store;

// 配置以后,创建是store
import { createStore,applyMiddleware, compose } from "redux";
import reducer from './reducer';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, /* preloadedState, */ composeEnhancers());
export default store;
  • 当这个图标变绿色就证明配置成了,可以调试了


    image.png
总结Redux的核心API
  1. createStore 绑我们创建store
  2. store.dispatch方法,帮我们派发创建的action,这个action会传递给store
  3. store.getState方法,帮我们获取到store里的所有的数据内容
  4. store.subscribe方法,帮我们订阅store里数据的改变,只要store发生改变,subscribe方法接收的函数,就会被执行。
如果说store是图书管理员,那么reducer就可以看作是图书记录手册,管理员需要通过记录手册来对数据进行操作。随着项目复杂度的增加,如果reduce存放过多的数据,可能造成代码的低可维护性,这个时候,我们就需要把一个reducer拆分成多个子的reducer,最后再做整合。具体怎么做呢?
  • 首先,创建子的reducer,比如我的Header文件夹里存放的都是Header组件的内容,那么这个时候,可以在Header文件夹下创建一个store文件夹,store文件夹下再创建一个reducer.js文件。

  • 然后在总的store文件夹下的reducer.js文件里,通过combineReducers方法进行整合。

// 把一些小的reducer合并成大的reducer
import { combineReducers } from 'redux';
import headerReducer from '../common/header/store/reducer';

export default combineReducers({
    // header是自定义的名字,可随意起名
    header:headerReducer
})
image.png
  • 整合以后,我们再取值的时候,就需要多一层。
// 没用拆分之前
const mapStateToProps = (state) => {
    return {
        focused:state.focused
    };
}

// 拆分后
const mapStateToProps = (state) => {
    return {
        focused:state.header.focused
    };
}
  • 当然了,像上边那样创建子reducer,在总的reducer引入子reducer的时候,层级有可能会很深。
import headerReducer from '../common/header/store/reducer';
  • 这个时候,可以给子reducer创建一个出口文件,index.js,然后在出口文件里,把recucer导出


    image.png
  • 然后在总的reducer里引入的时候,就可以这样


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

推荐阅读更多精彩内容