一步步带你入门Redux管理数据

闲聊

最近忙里偷闲学习了react。因为以前一直都是使用vue作项目,因此学习react的时候以为既熟悉又陌生。
 
熟悉是由于它和vue拥有许多类似的概念,包括都推崇组件化、都拥有’props’的概念、核心都是视图层框架等等。虽然react不像vue拥有那么多丰富的API,可是在我看来,正由于react自己没有过分的封装,再加上react的社区很是成熟与活跃,才使得react的开发灵活多变,相比起来,我以为react更适合大型项目的开发,react的函数式变成也更容易实现前端自动化测试。
 
尤大本身也说过vue从一开始的定位就是尽量的下降前端开发的门槛,让更多的人可以更快地上手开发。因此学习起来,vue更加圆滑,而react相对陡峭。二者在我看来都是很是优秀的框架,没有高低之分,咱们能够根据不一样的开发状况选择不一样的开发工具。前端

前言

今天主要是想写一下如何在react中管理数据。我会从搭建react项目开始,按部就班,若是你对这其中的某些过程已经很是了解,能够在右侧的目录中跳过该章节。vue

Redux

Redux=Reducer+Flux,Flux是Facebook推出的最原始的辅助React的数据层框架,可是它并非那么的好用,因此有人把Flux作了一个升级,变成了Redux。react

为何要使用redux

请看下面这张图
组件通讯ajax

假设底部绿色的组件要和最顶层的组件通讯,那么绿色的组件须要层层把消息转发给父级组件,直到传到最顶层的组件,若是咱们项目中的组件很是之多,组件之间又常常须要共享传值的话,那么使用react这种父子通讯的方式,整个项目的开发就会变得很是冗余,也不易维护
 
前面说过,react是一个视图层框架(并非什么问题都依靠react解决,react只解决数据和页面渲染——也就是搭建视图, 至于组件渲染交给别的数据层框架来作额外的支撑),因此咱们须要一个数据层框架去协助react帮助数据管理,目前主流和react搭配的就是redux
 
redux要求咱们把数据都存放在一个名为store的公共存储区域,咱们把数据都存放在store中。若是想经过绿色的组件改变数据传给其余组件,那么咱们只须要操做store就能够了,接着其余灰色的组件会自动感知到变化,而后从新去store中取数据,这样咱们取到的数据,就是刚刚绿色组件所更改的数据。也就是说,redux间接地帮咱们实现了组件通讯的功能,让咱们的组件通讯变得很是的轻松。
 算法

❗️可是咱们要知道,redux不是只为react服务的,而是为JavaScript服务的状态容器,react-redux才是专门为react服务的状态管理插件,本篇文章主要讲解redux。

redux 三大原则

1.单一数据源
store是惟一的。
整个应用的数据被储存在一棵object tree(对象树)中,而且这个 object tree 只存在于惟一一个 store 中。
 
2.state是只读的
惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
 
3.使用纯函数执行修改
为了描述 action 如何改变 state tree ,你须要编写 reducers。
reducer必须是纯函数: 纯函数是指给定固定的输入, 就必定会有固定的输出, 且不会有任何反作用; 一旦一个函数有一个settimeout或者ajax或者new Date相关内容的时候, 它就不是一个纯函数, 因此reducer里不能够有异步的操做。
❗️反作用: 例如对参数的修改就是反作用, 这个时候reducer也就不是一个纯函数了npm

Mutabilit(可变性) & Immutability(不变性)

在学习redux前,我但愿你能够了解Mutabilit(可变性)和Immutability(不变性)这两个概念。
 
首先从字面上理解,「可变」意味着能够出现变化,能够变化,就意味着可能会出现一些问题或是bug。
 
「不可变」就表明某些数据是不可修变的,若是想要改变不可变的数据,那么只能去复制旧的数据,再产生新的数据来取代旧的数据,咱们永远不要去修改旧的数据。
 
我这里不作过多的赘述,若是你对这块有兴趣,能够去自行查找一些文章了解,本文只须要你了解这个概念。redux

redux的工做流程

redux工做流程
reactComponents: 每个页面上的组件。
actionCreators:管理action的地方。
action:动做,它是 store 数据的惟一来源。通常来讲你会经过 store.dispatch() 将 action 传到 store,一般是一个对象。
store:存储数据的公共区域,也能够理解为把action和reducers联系到一块儿的对象。
reducers:处理不一样的action类型,告诉store该给组件什么样的数据,而后store再把这个数据给到对应的组件。
 
这里你或许会看的有点蒙,我下面用代码来解释一下redux的工做流程。数组

安装redux

npm安装 yarn安装
npm install --save redux yarn add redux

redux代码讲解

我想实现一个todoList功能,当我点击提交按钮的时候,在input下面会增长我刚刚输入的内容。
其中,input和button是父组件,下面的ul是子组件。
效果以下

todoList

基础结构-取值

先在刚刚搭建好的react项目中的src文件下创建一个store文件夹(你也能够建在任何的组件文件夹下),在store里分别建立一个index.js和reducer.js。
 
reducer.js框架

// 定义初始数据defaultState,若是不给state设置一个初始数据,那么最初state就是一个undefined。
// 这里我已经为todoList写入了一个字符串inputValue和数组list。
const defaultState = {
  inputValue: '',
  list: ['默认数据1', '默认数据2']
};
export default (state = defaultState, action) => {
  // state指的是上一次存储的数据, action是组件传过来的内容
  return state;
}

 
index.jsdom

// 从redux引入createStore方法
import { createStore } from 'redux'; 
// 从刚刚建立的reducer.js引入reducer
import reducer from './reducer';
// 定义一个名为store的redux存储区,咱们把reducer做为参数传入createStore方法来构造这个存储区,store里的数据只能够经过reducer来修改。
const store = createStore(reducer);

// 导出store
export default store;

 
建立子组件List.js

import React from 'react';

const List = (props) => {
    return (
      <div>
        <ul>
          {
            props.list.map((item, index) => {
              return <li key={index}>{item}</li>
            })
          }
        </ul>
      </div>
    );
}

export default List;

❗️此处的List组件是一个无状态组件,没有任何的逻辑操做,全部逻辑操做交由父组件执行。
 
接着修改你的App.js (我这里把App.js做为父组件)

import React, { Component } from 'react'
import store from './store'
import List from './List'

export default class App extends Component {
  constructor(props) {
    super(props);
    // 用store的getState()方法取出store的数据,再赋值给this.state
    this.state = store.getState();
  }
  render() {
    return (
      <div>
        <input type="text"/>
        <button>提交</button>
        <List list={this.state.list}></List>
      </div>
    )
  }
}

此时运行出来应该是这样
todoList
目录结构
目录

修改store

此刻咱们已经能够取到store里的数据了,那么咱们如今想在点击提交的时候,list里新增一条数据,而且实时地响应出来,应该怎么作呢。
 
修改App.js

import React, { Component } from 'react'
import store from './store';
import List from './List'
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
    // 修改事件的this指向,不然this指向undefined
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }
  render() {
    return (
      <div>
        <input
          type="text"
          onChange={this.handleInputChange}
          value={this.state.inputValue}
        />
        <button onClick={this.handleClick}>提交</button>
        <List list={this.state.list}></List>
      </div>
    )
  }
  handleInputChange(e) {
    // 1) 建立action
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    // 2) 传给store
    store.dispatch(action);
    // 3) store若是接收到了action, 会自动把以前的数据和action传给reducer (这步store帮咱们作了)
  }
  handleClick() {
    const action = {
      type: 'add_todo_item',
    }
    store.dispatch(action);
  }
}

 
而后修改咱们的reducer

const defaultState = {
  inputValue: '',
  list: ['默认数据1', '默认数据2']
};
// 4) reducer拿到以前的数据和当前操做的信息后对数据进行处理,而后返回新的数据给store
export default (state = defaultState, action) => {
  const newState = JSON.parse(JSON.stringify(state)); //深拷贝,由于reducer能够接收state, 但毫不能修改state 因此要拷贝state
  switch (action.type) {
    case 'change_input_value':
      newState.inputValue = action.value;
      return newState; //return给了store
    case 'add_todo_item':
      newState.list.push(newState.inputValue);
      // 添加成功后清空inputValue
      newState.inputValue = '';
      return newState;
    default:
      break;
  }
  return state;
}

此时咱们会发如今input框里输入数据页面是没有反应的,点击提交,页面上也没有发生任何变化,别急,咱们先来打印一下store,这也是咱们学redux时常常容易犯的错误。
 
咱们在handleClick方法的最后,用store.getState()方法来打印一下store的值
❗️注意是最后,store.dispatch(action)的后面

console.log(store.getState());

打印store
咱们发现store里的数据已经被改变了,list增长了1条数据,inputValue也被清空了,这证实咱们以前在reducer中编写的代码都生效了,可是都并无渲染在页面上。如今页面上input的value值是空值,是由于一开始inputValue的值就是空,而不是咱们后来清空的。这一切都由于咱们并无在组件中去监听更新store里的数据,咱们应该在页面中监听store,当store发生变化时,实时更新咱们的数据。

监听store

App.js最终代码

import React, { Component } from 'react'
import store from './store';
import List from './List'
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
    // 5) 监听store的变化
    // 订阅store, 只要store发生改变, subscribe里的函数就会被自动执行
    this.handleStoreChange = this.handleStoreChange.bind(this);
    store.subscribe(this.handleStoreChange);
  }
  render() {
    return (
      <div>
        <input
          type="text"
          onChange={this.handleInputChange}
          value={this.state.inputValue}
        />
        <button onClick={this.handleClick}>提交</button>
        <List list={this.state.list}></List>
      </div>
    )
  }

  handleInputChange(e) {
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    store.dispatch(action);
  }

  handleClick() {
    const action = {
      type: 'add_todo_item',
    }
    store.dispatch(action);
  }

  handleStoreChange() {
    // 6) 当感知到store变化的时候, 调用store.getState()方法从store中从新取一次数据, 而后调用setState替换掉当前组件中的数据, 这样就会同步数据了
    this.setState(store.getState());
  }
}
❗️咱们上面说过,不要直接更改state的值,因此咱们每次修改时都建立了一个新的state,返回的也是全新的state。
不过,大量重复的代码就是问题的源泉,咱们在编写代码时,理应去减小出现bug的可能性。因此,当咱们平常开发时,我推荐使用immutable.js或一些其余的第三方库——咱们在最初就把state生成immutable对象, 这样能够百分百保证state不会被改变。

总结

拿刚刚的例子来讲,咱们首先把input的值和store中的inputValue关联到了一块儿,若是你想修改input框的value值,就必须经过修改store中的inputValue实现。咱们用onChange事件监听了input,在每次修改input中的值的时候,咱们都建立了一个action,并把这个action派发给了store。
 
store接收到了这个action,会自动把这个action传给reducer。reducer拿到这个action,开始对比action的type值,并进行相应的数据操做,以后返回了一个新的数据给store。咱们在组件内监听了store的变化,因此当reducer把值返回给了store,store更新了本身的数据,咱们的组件就会监听到刚刚store的变化,随之更换组件内store的数据。
 
input输入流程:App→store→reducer→store→App检测到store发生变化,更新数据,渲染页面
 
点击提交流程:App→store→reducer→store→App.js检测到store发生变化,更新数据→父组件App从新渲染触发子组件List更新渲染
 

优化

写到这里,若是你只想了解该怎么使用redux,那么至此以前的代码应该已经足够让你上手去使用redux了。可是其实上面的代码中还有不少能够优化的地方,我没有直接把优化事后的代码写出来是怕不易于初学者阅读学习,容易看晕。
 
好比说咱们应该利用actionTypes统一常量, 预防因拼写引起的bug,以及将action的建立放到actionCreators中统一进行管理。这样作的优势除了提升代码的可维护性,还能够方便自动化测试。
 
在实际开发中,redux也应遵守组件化开发,建议每一个组件都应该拥有本身的store文件夹,src目录下的store应仅仅做为各个组件内store的集合。
 
在子组件List上,咱们使用数组的index做为key值并非一个好的作法。事实上我认为不到万不得已的状况不要使用index做为key值。由于列表每一项的顺序均可能会发生变化(好比说咱们若是删除list中的某一项时,list的顺序就发生了变化,list中每一项的index值都发生了改变),react又是经过diff算法去渲染页面的,diff算法经过key值去对比虚拟dom,若是key值所有发生改变,那虚拟dom便会所有更新,这明显会下降咱们的性能,因此说使用数组的index做为key值是下下策,有兴趣的话能够去看看这篇文章深度解析使用索引做为 key 的负面影响
 
由于diff算法(虚拟dom从顶层 层层比对)的缘由,因此在父组件内只要一改变inputValue的值,子组件就会从新渲染,即便咱们并无修改list数据。这一样会下降咱们的性能,试想一下,若是你拥有很是多的子组件,父组件输入任何一个字符都会致使全部子组件的从新渲染,这会消耗多少的性能呢?
为了解决这种多余性能的消耗,咱们应该在子组件内利用react内置的生命周期函数shouldComponentUpdate去阻止子组件跟随父组件去执行无谓的render函数,这样就能够避免虚拟dom的比对,提高性能。
 

这篇文章到这里就所有结束了,原本想在一篇里把redux和react-redux都写出来,可是怕太长了,因此下次找时间再写react-redux吧。
 
若是你对这篇文章有任何疑问或补充,均可以在评论区给我留言讨论。
若是你有兴趣,还能够来个人 博客看个人最新更新,我平时还会总结一些日语的小知识,喜欢日语的小伙伴也能够和我一块儿沟通讨论。
 
顺便一提最近看了排球少年的动漫,虽然比较冷门可是真的是一部不可多得的好做品,不管你喜不喜欢排球我以为你看了这部动漫后都会爱上它的,强烈安利一波。
 
你们晚安啦。
乌野高校排球部
相关文章
相关标签/搜索