React-Redux学习小记

来源:互联网 发布:逗鱼时刻知乎 编辑:程序博客网 时间:2024/06/03 03:54

上一篇中较为系统的学习了一遍React-React学习小记
基本知道了React是什么,它跟jquery的区别。

利用React,可以利用组件式的思想进行前端代码的编写,而在组件(component)中,又可以按照组件的声明周期来进行较为精确的控制,在不同的声明周期中,可以嵌入其他例如jquery来进行定时任务以及ajsx请求等。同时,React以状态机以及虚拟DOM的思想去管理整个component。一旦state变化,会引起相应component的重新渲染。

下面进入正题:

Flux介绍

说Redux,不得不先介绍Flux,Flux是一种编程的思想,专门用于解决软件的架构问题,就像MVC思想解决web网站架构一样。
Flux将一个应用分为4个部分:

  • View: 视图层,用于显示的
  • Action(动作):视图层发出的消息(比如鼠标点击实践)
  • Dispatcher(派发器):用来接收Actions、执行回调函数,不同的Action,可以对应于不同的处理。
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Flux最大的特点,就是单项流动,基本的流程为:

  • 1、用户访问 View
  • 2、View 发出用户的 Action
  • 3、Dispatcher 收到 Action,要求 Store 进行相应的更新
  • 4、Store 更新后,发出一个”change”事件
  • 5、View 收到”change”事件后,更新页面

Redux?

前面简短的介绍了Flux的思维,而Redux就是将Flux思维加以实现的。
Redex的设计思想:

1、Web 应用是一个状态机,视图与状态是一一对应的。2、所有的状态,保存在一个对象里面。

对比前面所讲的React,如果字面意思来说,是差不多的。

下面一一介绍Redux的基本概念:

Store

Store就是一个用来存放数据的地方,整个应用就只能有一个Store,可以使用:

import { createStore } from 'redux';const store = createStore(reducer);

可以使用这样的方法来生成store。

store.dispatch()

上面说过,由view去发出Action,从而才可以往下进行。那么view又是如何发Action的呢?
view发出Action的唯一方法就是通过store.dispatch() store就像一个路由器,view给命令给store,由store发出Action。
例如在view中你可以这样写代码:

store.dispatch(addAction('Hello Redux'));

这样以来,view就成功发送一个Action出去了。

store.subscribe()

前面说过,view,是通过store去往外发送Action的,这样一来,在store里面有一个subscribe(fn)的函数,它主要用于监听state,一旦state发生变化,就会执行subscribe注册的这个fn函数。

store里面主要方法:

  • store.getState()
  • store.dispatch()
  • store.subscribe()

生成一个store

如下,实现主要的三个方法的基本store:

const createStore = (reducer) => {  let state;  let listeners = [];  const getState = () => state;  const dispatch = (action) => {    state = reducer(state, action);    listeners.forEach(listener => listener());  };  const subscribe = (listener) => {    listeners.push(listener);    return () => {      listeners = listeners.filter(l => l !== listener);    }  };  dispatch({});  return { getState, dispatch, subscribe };};

State

React里面也有State,其概念也相同,State就好比某一个时刻的Store,由于State决定这页面的渲染,在一个应用中,一个State,就对应
唯一一个页面。
如下,我们可以这样获得State:

const state = store.getState();

Action

从名字来说,就是动作的意思,一般是由view来发出(触发)的。类似于通知者,view发出通知,用什么发呢?通过Action发。
Action是一个对象,里面的type属性是必须的,其他的可以自由增加。
看一个一般的Action:

const action = {  type: 'GET_DATA',  payload: myData};

上面的这个简单Action意思可以简单理解为:view发出了一个GET_DATA,并且把一份名为payload的数据,也给了Action。接下来的事情就是各种处理,然后更新state从而引发更新view。

那么如何能够产生一个Action呢?
由上面可以知道Action是一个对象,并且有基本的结构,那么就简单了,如下的addAction就是一个Action:

const ADD_TODO = '添加ACTION';function addAction(text) {    return {        type: ADD_TODO,        text    }}

里面除了基本的type外,还有一个text参数。

Reducer

首先: Reducer是一个纯函数
一个纯函数必须满足一下特征:

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

那么view把Action发送出去后,又会怎么样呢?此时就到了Reducer这里,Reducer是一个函数,它接受Action和当前State作为参数,然后返回一个新的State .
看看一小段代码能够更好的理解:

const reducer = (state = defaultState, action) => {  switch (action.type) {    case 'ADD':      return state + action.payload;    default:       return state;  }};

如上,一个简单的reducer就是这样,可能内部有多个Action.type,所以会对应不同的Action的处理。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
store和对应的reducer绑定起来的代码:

import { createStore } from 'redux';const store = createStore(reducer);

上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

接下来看一张图,从这张图可以很简单的了解到Redux的工作流程:
这里写图片描述

简单描述下上述流程:
- view利用store发送Action
- 然后会到redux里面执行纯函数
- 最终会返回一个新的state
- 由于state改变,从而引发相应component的重新渲染

Redux中的中间件

Redux中间件?前端也需要?
这里回想普通的redux的流程:view发送一个Action,然后reducer就执行,然后就一步一步走下去了,在这里面,Action貌似只代表一个实体,没有任何动作的实体。
那如果,我要先做点事,然后在把结果给reducer去处理呢?
比如ajax请求!
因为reducer是纯函数嘛。
所以就需要先去拿数据,拿完之后,在根据情况发送不同的Action,此时就要引入Redux中间件。

中间件怎么用?

对于引入中间件的操作,还是需要在store里面进行的,以下例子是引入redux-logger 中间件:

import { applyMiddleware, createStore } from 'redux';import createLogger from 'redux-logger';const logger = createLogger();const store = createStore(  reducer,  applyMiddleware(logger));

上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

当然,你也可以引入多个中间件的功能:

  applyMiddleware(thunk, promise, logger)

但是得注意,thunk,promise,logger这三个中间件的引入,是有次序的,具体需要查文档。

applyMiddleware函数
这个函数是干嘛用的呢?
它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。

redux-thunk 中间件

估计读者记得store里面有个dispatch函数,用来给view发送Action的。而Action是什么呢?是一个对象,构造例如下面:

dispatch({      type:CHAT_LOGIN,    data:req}); 

redux-thunk能干什么呢?
当把thunk引入后,你可以以这样的一种方式进行dispatch

dispatch(fn)

没错,可以往里面丢一个函数。
看下面一个例子:

const fetchPosts = postTitle => (dispatch, getState) => {  dispatch(requestPosts(postTitle));  return fetch(`/some/API/${postTitle}.json`)    .then(response => response.json())    .then(json => dispatch(receivePosts(postTitle, json)));  };};// 使用方法一store.dispatch(fetchPosts('reactjs'));// 使用方法二store.dispatch(fetchPosts('reactjs')).then(() =>  console.log(store.getState()));

往dispatch里面丢了一个fetchPosts('reactjs'),而fetchPosts('reactjs') 实际上先dispatch了,然后再返回了一个fetch函数,fetch是一个新型的ajax方法。先获取了json,然后再返回一个Action:receivePosts(postTitle, json)

是否有体会到thunk中间件的作用了呢?

React-Redux

关于ReactRedux也了解了差不多了,那么现在应该如何在项目中使用到这个框架呢?
React-Redux将所有的组件分为了UI组件容器组件,
其中,UI组件有以下几个特征:

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

通俗点讲,就是它只负责显示,显示的数据由props提供。
而容器组件则有下面几个特征:

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

容器组件就是来跟后台交互,然后处理数据再返回给UI组件的。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
所以,你只需要编写基本的UI组件,然后把它交给React-Redux就行了。

connect()

如果看过React-Redux项目同学,应该能够发现往往最后一行代码会有connect() 函数,那么这个函数到底有啥作用呢?
connect()函数主要是用于生成容器组件 ,换句话说,就是让UI组件和容器组件连起来 的用途。

看一个简单例子:

import { connect } from 'react-redux'const VisibleLogin = connect(  mapStateToProps,  mapDispatchToProps)(Login)

上面代码中,可以看到使用了connect()函数,并且返回了一个VisibleLogin,这个就是就是由 React-Redux 通过connect方法自动生成的容器组件

那么问题来了,中间两个mapStateToPropsmapDispatchToProps是啥子东西呢?
前面讲过,UI组件是没有状态的,他只能使用Props,而Action是由用户触发在UI上发送的,现在回过来看看这两个函数,大概有点感觉了吧:

  • 输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
  • 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。

    看一个实际中的例子:

let mapStateToProps = ( state ) => {    let { sessions, user } = state.chatIndex;    return {        _sessions: sessions,        _user: user    };};let mapDispatchToProps = ( dispatch ) => {    return {        //把整个Actions都和dispatch绑定起来        ACTIONS: bindActionCreators( actions, dispatch )    };};//login这个组件。连接,把UI和容器连接。export default connect( mapStateToProps, mapDispatchToProps )( Login );

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。比如上面代码,在mapStateToProps里面,把state里面的_sessions_user 传递到Props里面了,所以在这个Login组件里面可以使用这两个数据。
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

组件

connect方法生成容器组件以后,必须让容器组件拿到state对象,才能通过mapStateToProps传递给UI组件。
一种方法是将state作为参数,利用子组件的props,一层一层传下去,但这样编程起来比较麻烦。
另一种方法就是利用React-Redux提供的Provider组件,这样就可以让容器组件拿到state了。
这个写起来比较简单:

import React from 'react';import ReactDOM from 'react-dom';import {Provider} from "react-redux";import Store from "src/store";import App from 'src/components/App';import Chat from 'src/pages/Chat/Index';//由Provider组件传递store,然后一层一层接着可以通过props来往下传递,ReactDOM.render(  <Provider store={Store}>    <App>    <Chat />    </App>  </Provider>,  document.getElementById('app'));

一般<Provider>组件是作为顶层组件使用,这样整个页面的所有组件,就都可以得到Store里面的所有东西了。

东西写的有点杂,有哪里不对的地方,欢迎提出来大家一起学习!

参考资料:
http://www.ruanyifeng.com/blog/javascript/