专栏首页全栈前端精选彻底让你理解redux

彻底让你理解redux

临危受命,一周各种熬夜突击redux,好吧,刚开始真的各种香菇蓝瘦,前天还熬到凌晨五点多,写了点demo,有点感觉,一点点记录下。这里简单介绍下Redux以及其与react结合的使用方法

我们为什么需要Redux,什么是Redux

state

state才是真正的前端数据库,它存储着这个应用所有需要的数据。 这里拿一个简单的例子说明下,为什么说简单的例子呢,因为简单到不应该使用redux。。。 运行效果如图(学习redux这个例子被介绍烂了):

项目的运行效果大家应该能猜到哈,猜不到的clone下项目几几运行下:-) 所以这里的如说问你,这个应用应该存在数据里什么数据呢?对的,就一个count,所以数据库就存一个count就可以了,同理,这个应用的state其实就酱紫:

注释错了,是count值为3 .╥﹏╥...

这里展示的不是很明显,可以拿我们下一篇博客的demo来查看下它的 state

所以说道这里,大家对于state就已经明白了,需要说明一下的是,一个应用只应该有一个state。对,不管多大,就一个!

action

既然这些state已经有了,那么我们是如何实现管理这些state中的数据的呢,当然,这里就要说到action了。 什么是action?E:action,中:动作。 是的,就是这么简单。。。 只有当某一个动作发生的时候才能够触发这个state去改变,那么,触发state变化的原因那么多,比如这里的我们的点击事件,还有网络请求,页面进入,鼠标移入。。。所以action的出现,就是为了把这些操作所产生或者改变的数据从应用传到store中的有效载荷。 需要说明的是,action是state的唯一来源。 action也没有什么神秘,本质上就是一个JavaScript对象,但是约定的包含type属性(你总得告诉你这个action是啥嘛),可以理解成每个人都要有名字一般。除了type属性,别的属性,都可以DIY~ 那么这么多action一个个手动创建必然不现实,一般我们会写好action creator,即action的创建函数。调用action creator,给你返回一个action。

比如这个counter应用,我们就有两个action,一个decrement,一个increment。 所以这里的action creator写成如下:

export function decrement() {
   return{
       type:DECREMENT_COUNTER
   }
}export function increment(){
   return{
       type:INCREMENT_COUNTER
   }
}

好吧,藏也藏不住了,你也发现了这里有另外两个按钮,干嘛的?奇数是点击+1、延迟+1,对应的action creator:

export function incrementIfOdd(){
   return(dispatch,getState)=>{
       const {counter} = getState();
       if(counter%2==0) {
           return;
       }
       dispatch(increment());
   }
}export function incrementAsync() {
   return dispatch => {
       setTimeout(() => {
           dispatch(increment());
       }, 1000);
   };
}

好吧,又被你发现了,不是说action creator返回的是一个action对象么,你这返回的什么鬼?这里留个疑惑好吧,简单的解释, 为什么可以这么用呢,因为我用了中间件呀~(后续会介绍)

为了减少样板代码,我们使用单独的模块或文件来定义 action type 常量

 export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

这么做不是必须的,在大型应用中把它们显式地定义成常量还是利大于弊的。

如果我都快把action说完了你还是不懂action是什么的话,你就理解成,一个可能!改变state的动作包装。

reducer

既然这个可能改变state的动作已经包装好了,那么我们怎么去判断并且对state做相应的改变呢?对,这就是reducer干的事情了。 从一开始我们就说明下一个概念:

reducer决定了state的最终格式。

reducer是一个纯函数,也就是说,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

reducer对传入的action进行判断,然后返回一个通过判断后的state,这就是reducer的全部职责。 从代码可以简单地看出:

import {INCREMENT_COUNTER,DECREMENT_COUNTER} from '../actions';export default function counter(state = 0,action) {
   switch (action.type){
       case INCREMENT_COUNTER:
           return state+1;
       case DECREMENT_COUNTER:
           return state-1;
       default:
           return state;
   }
}

这里我们就是对增和减两个之前在action定义好的常量做了处理。

对于一个比较大一点的应用来说,我们是需要将reducer拆分的,最后通过redux提供的combineReducers方法组合到一起。 如此项目上的:

const rootReducer = combineReducers({
   counter
});export default rootReducer;

虽然这里我们就一个counter (ಥ_ಥ) 但是道理你懂得。

这里你要明白:每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。 combineReducers() 所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, 然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。

store

store是对之前说到一个联系和管理。具有如下职责

  • 维持应用的 state;
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

后面两个不怎么用哈~

再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。 store的创建通过redux的createStore方法创建,这个方法还需要传入reducer,很容易理解:毕竟我需要dispatch一个action来改变state嘛。 应用一般会有一个初始化的state,所以可选为第二个参数,这个参数通常是有服务端提供的,传说中的Universal渲染。后面会说。。。 第三个参数一般是需要使用的中间件,通过applyMiddleware传入。

说了这么多,action,store,action creator,reducer关系就是这么如下的简单明了:

接合react-redux的使用

说到react-redux,必然想到react和redux,是的,react-redux正是redux和react的桥梁工具。

react-redux将组建分成了两大类,UI组建和容器组建。 简单的说,UI组建负责美的呈现,容器组件负责来帮你盛着,给你"力量"

好吧,官方点: UI 组件有以下几个特征:

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API

如:

export default class Counter extends Component{
   render(){
       const {counter,increment,decrement,incrementIfOdd,incrementAsync} = this.props;
       return(
           <p>
               Clicked:{counter} times
               {'  '}
               <button onClick={increment}>+</button>
               {'  '}
               <button onClick={decrement}>-</button>
               {'  '}
               <button onClick={incrementIfOdd}>increment if Odd</button>
               {'  '}
               <button onClick={incrementAsync}>increment async</button>
           </p>
       )
   }
}

容器组件特性则恰恰相反:

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API

如:

class App extends Component{
   render(){
       const {counter,increment,decrement,incrementIfOdd,incrementAsync} = this.props;
       return(
           <Counter
               counter={counter}
               increment={increment}
               decrement={decrement}
               incrementIfOdd={incrementIfOdd}
               incrementAsync={incrementAsync}/>
       )
   }
}App.propTypes = {
   counter:PropTypes.number.isRequired,
   increment:PropTypes.func.isRequired,
   decrement:PropTypes.func.isRequired,
   incrementIfOdd:PropTypes.func.isRequired,
   incrementAsync:PropTypes.func.isRequired
};export default connect(
   state=>({
       counter:state.counter
   }),
   ActionCreators
)(App);

说到这里大家应该都懂,那么问题来了,redux和react如何产生点关系呢??不难想到,如果产生关系肯定只要跟容器组件产生关系就可以了,毕竟他是react这些组件的老祖宗。 那么如何产生关系呢??对的,就是上面代码里的,react-redux中的connect方法。

connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props), 后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

比如这样:

export default connect(
 state => ({ counter: state.counter }),
 ActionCreators
)(Counter);

因为作为组件,我们只要能拿到值,能发出改变值得action就可以了,所以mapStateToProps和mapDispatchToProps正是满足这个需求的。

当时对这个connect也是好一顿理解

这么通俗的说你该明白了吧

可以吧所有的组件想象成装在一个罐子里,这个罐子使用container做的,然后这个罐子的唯一的口就是里面的东西想要去改变的唯一途径。 说白了,这个口,就是connect,而redux中的所有的组件都是在罐子外面的。

reducer是改变state的,state就可以可以理解成组件的粮食,需要的时候redux就把粮食通过dispatch投入到罐子里。 那么我怎么知道你需要呢?所以我们之间得有个约束,你喊一声饿了,我就知道你要吃饭了,你喊一声渴了,我就知道你要喝水了。 这些动作,就是你发出的action,喊得词语,饿了,渴了,就是action.type,然后redux拿给军事reducer解读下,到底给罐子里投入什么。

不知道这么通俗的解释有没有说明白 (T_T)

所以这样看来,组件通过container包装以后和redux就可以说是完全隔绝了,组件就是做组件的事情,redux就是做redux的事情。中间的枢纽是connect。 这也就说明了,redux并不是只服务我们react哒~也即是我这一套逻辑在罐子外面,罐子里面是什么其实我并不是很在意。。。只要我们预定好action和state就可以了。

所以。。。redux也并没有特别神秘的地方。 当然,这里只是简单的说了下redux的简单概念,并没有特别深入的讲解。 关于redux的异步操作,以及在服务端的运行(node),universal渲染,结合react-router的使用等等等的功能,咱再慢慢了解慢慢研究慢慢总结哈~

ヾ(^▽^ヾ)

本文分享自微信公众号 - 全栈前端精选(isNealyang),作者:Nealyang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-09-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【THE LAST TIME】从 Redux 源码中学习它的范式

    「范式」概念是库恩范式理论的核心,而范式从本质上讲是一种理论体系。库恩指出:「按既定的用法,范式就是一种公认的模型或模式」。

    Nealyang
  • 完全理解 redux(从零实现一个 redux)

    记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、store、dispatch、middleware 等等,我...

    Nealyang
  • 从 Redux 设计理念到源码分析

    Redux 也是我列在 THE LAST TIME 系列中的一篇,由于现在正在着手探究关于我目前正在开发的业务中状态管理的方案。所以,这里打算先从 Redux ...

    Nealyang
  • 斯坦福CS231n - CNN for Visual Recognition(6)-lecture5预处理、正则化、损失函数

    关于数据预处理我们有3种常用的方式,假设数据矩阵XX,假设其尺寸是[N,D][N ,D](NN是数据样本的数量,DD是数据的维度)。

    李智
  • Vuex之getter

    Vuex提供给我们一个Getter方法,主要就是为了在数据派生一些状态,简单来说就是想要通过判断进行过滤。

    wade
  • Springboot之自动注册DispatcherServlet

        Springboot中我们引入spring-boot-starter-web依赖后,web就自动配置好了,在web.xml的年代,我们需要在web.xm...

    克虏伯
  • G -Star 2017回顾|《绝地求生》成G-Star最大亮点,carry全场!

    VRPinea
  • 《Kaggle项目实战》 泰坦尼克:从R开始数据挖掘(一)

    摘要: 你是否为研究数据挖掘预测问题而感到兴奋?那么如何开始呢,本案例选自Kaggle上的数据竞赛的一个数据竞赛项目《泰坦尼克:灾难中的机器学习》,案例涉及一...

    小莹莹
  • 使用Redis之前5个必须了解的事情

    使用Redis开发应用程序是一个很愉快的过程,但是就像其他技术一样,基于Redis的应用程序设计你同样需要牢记几点。在之前,你可能已经对关系型数据库开发的那一整...

    李海彬
  • WPF 跳动的文字

    先上代码 public MainWindow() { InitializeComponent(); ...

    hbbliyong

扫码关注云+社区

领取腾讯云代金券