上一篇中较为系统的学习了一遍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
关于React
和Redux
也了解了差不多了,那么现在应该如何在项目中使用到这个框架呢?
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
方法自动生成的容器组件。
那么问题来了,中间两个mapStateToProps
和mapDispatchToProps
是啥子东西呢?
前面讲过,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里面的所有东西了。
东西写的有点杂,有哪里不对的地方,欢迎提出来大家一起学习!