app.jsx源码如下:
import React from 'react';
import { render } from 'react-dom';
import { bindActionCreators, createStore, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';
import Deskmark from 'components/Deskmark'; //项目的容器组件
import rootReducer from 'reducers';
import * as actionCreators from 'actions';
import 'bootstrap/scss/bootstrap.scss';
// create store with middlewares
const store = applyMiddleware(
thunkMiddleware
)(createStore)(rootReducer);
// create root component based on component Deskmark
const App = connect(
state => ({ state }),
dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
})
)(Deskmark);
// create DOM container
const container = document.body.appendChild(
document.createElement('div')
);
// render root conponent with store to DOM container
render(
<Provider store={store}>
<App />
</Provider>,
container
);
需要理顺以下问题:
1. bindActionCreator的作用和工作原理;
2. createStore的作用和工作原理;
3. applyMiddleware的作用和工作原理;
4. Provider的作用和工作原理;
5. connect的作用和工作原理;
6. thunkMiddleware的作用和工作原理;
Flux理念(大致了解即可)
为什么要推出Flux理念:
(1)React Component的初衷是提供界面级别的组件方案,因此将业务逻辑写进React Component。但是随着应用逻辑变得复杂,Component内部的业务逻辑随之膨胀,会损失复用性和可维护性。
(2)因此,需要将业务逻辑从视图组件中抽出来,分为action的构造行为(action creator)和响应action对数据进行更新的行为(reducer),这样数据、逻辑和展现各自分立,清晰明了。
核心概念:单项数据流
Flux的整个流程:
(1) Action: 即一个描述行为的json对象;
(2) Dispatcher:是一个信息的分发中心,也是Action和Store的连接中心。
Dispatcher可以使用dispatch方法执行一个Action,
并且可以用register方法注册回调,回调中处理Store的内容。
(3) Store: Store处理完后,它可以使用emit方法向其它地方发送命名为'change'的广播,通知它们Store已经变更.
(4) View: View层监听change事件,一旦change事件被触发,View层调用setState来更新整个UI。
Redux概要
Redux是官方提供的一种基于Flux理念的一种实现,是一种状态容器,提供了可预测的状态管理。
1. 为什么需要状态管理?
状态起始就是应该用运行时需要的各种各样的动态数据,诸如:服务器返回的数据,本地生成还没有持久化到服务器的数据,本地缓存数据,服务器数据加载状态,当前路由等.
2. 状态管理容器那么多,为何选用Redux?
React是一个由状态整体输出界面整体的View层实现,而Redux着眼于对状态整体的维护,不会产生出具体变动的部分。因此React搭配Redux很适合.
3. Redux是如何让应用状态(state)可预测的?
Redux的三大定律:
'单一数据源': 即整个应用的state存储在一个称之为store的js对象中;
'state只读': 即只能通过触发action来改变state, 而不能直接在state上面修改数据;
'使用纯函数执行修改': reducer是纯函数,接受先前的state和待处理的action,返回新的state.
4. store和state的区别是什么?(自己老是易混淆)
state是用于整个应用数据保存,是一个实实在在的数据对象;store是一个综合体,里面既有state,也有访问state的值的方法(store.getState()),也能通过dispatch()执行一个action,也可通过subscrible(listener)注册回调,监听state的变化。
Redux的三大组成
1. action: 是一个描述行为的json对象,利用store的dispatch方法,可以被传递到store中(可看出,action是store的唯一信息来源).
//原始写法
const CREATE_POST = 'CREATE_POST';
let createPostAction = {
type: CREATE_POST,
data: {id: 1, title: 'testTitle'}
}
//改进写法
function createPost(data) {
return {
type: CREATE_POST,
data: data
}
}
写成actionCreator的意义在于:Redux中,actionCreator返回一个action对象(并非像Flux理念中直接调用dispatch方法),这异步应用中,可以条件执行或者延迟执行。??
2. reducer:定义整个程序的state如何响应(接受先前的state和待处理的action,返回新的state)
function posts(state=initalPostState, action) {
switch(action.type) {
case CREATE_POST:
return [...state, action.data];
case DELETE_POST:
return state.filter((post) => {
return post.id != action.id;
});
default:
return state;
}
}
function user(state=initailState, action) {
switch (action.type) {
case USER_LOGIN:
return Object.assign({}, state, {
isLogin: true,
userData: action.data
});
default:
return state;
}
}
为了方便多人协作、文件结构清晰、代码逻辑合理,上面两个reducer建议分文件写。但是整个应用的state数据都存储在一个对象中,因此需要将两个/多个reduer合并:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
posts,
user
});
3. store: store是action和reducer的黏合剂(连接中心),介于二者之间。
(1)保存整个程序的state;
(2)可通过getSate()访问state的值;
(3)可通过dispatch()执行一个action;
(4)可通过subscribe(listener)注册回调,监听state的变化。
现在实现Redux的store:
import { createStore } from 'redux';
let store = createStore(rootReducer);
即将rootReducer传给redux提供的store创建函数:createStore。因为store要将action用dispatch方法传给reducer。
现在可以使用store:
(1). console.log(store.getState()); //打印store的初始状态
(2). store.subscrible( () => {
console.log(store.getState());
}); //监听state的变化,每次变化后都打印出新的state
(3). store.dispatch( createPost({id: 1, title: 'titleTest'});
//即将该actionCreator创建的action发送给reducer。
Redux的中间件:middleware
1. middleware触发时机:在action被dispatch分发(触发)之后,在调用最终reducer之前。
2. middleware可以接触到: action, store的getState和dispatch方法(原因:)
3. middleware的工作内容:
(1)在原有的action基础上创建一个新的action和dispatch(action转换,用于可以不action处理等),
(2)也可出发一些额外的行为(如日志记录),
(3)最后middleware可通过next触发后续的middleware和reducer本身的执行。
【反思】:
(1)为什么触发时机要放在action被dispatch触发的时候(注意,不是之后,而是当时!需要揣摩,目前已经搞懂),调用最终reducer之前?
答: 因为reducer的作用是接受原有的state和待处理的action,返回新的state。
一旦有了新的state, 即store有了变更,render便会自动更新相关UI。
即一旦调用reducer,后续没有时机再执行操作。
而action是用户的行为描述,中间件肯定在这之后。
(2)dispatch哪儿来的?
答:当使用redux提供的createStore时,返回的store即带有dispatch方法。
该dispatch方法会在store内部将action交给reudcer处理。
其中,reducer都是传进createStore(store的创建函数)中来的,action是业务代码中手动传给store.dispatch()的(初级,后文需要升级)。
(3)action具体是什么时候传给createStore()的?
//源码(初级,后文需要升级)
import { createStore, combineReducers, applyMiddleware } from 'redux';
let rootReducers = combineReducers({
reducer1,
reducer2
});
let store = createStore(rootReducers, applyMiddleware(logger, creashReporter));
store.dispatch(addTodo('test first')); //现在的store.dispatch()已经有了两个中间件的作用。
【重要!】源码1中的action是手动传给store.dispatch()的(后文会升级);
Redux的applyMiddleware
既然上文提到,middleware的功能是作用于store.dispatch,返回一个增强型的store.dispatch。那么需要一个方法将middleware和store.dispatch结合起来,我们称之为applyMiddleware。
//旧版风格
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const store = createStoreWithMiddleware(rootReducers);
//新版风格
const store = applyMiddleware(
thunkMiddleware
)(createStore)(rootReducer);
解释:
因为 middleware 最终是用来增强 store.dispatch的,
因此 applyMiddleware(thunkMiddleware)作为一个整体,
需要接受createStore作为参数,返回一个增强型的creatStore(提供增强型的 store.dispatch);
中间件 thunkMiddleware的作用
中间件的作用是返回一个增强型的dispatch。
thunkMiddleware中间件的存在,除了可以dispatch一个普通的action对象,还可以dispatch一个函数(即thunk)。
在该函数中,可以完成一些逻辑,诸如:延迟dispatch、条件性dispatch、在该thunk中多次dispatch等等。
中间件 promiseMiddleware的作用
当有异步的action(即action是一个函数,即是一个上文提到的thunk),有一种做法是定义三个action type(如FETCH_LIST_PENDING、FETCH_LIST_FULLFILLED、FETCH_LIST_REJECTED)。然后在发起行为,成功和失败时,分别dispatch这3中type的action。
自动化完成上述行为,具体如何自动化,见下面源码:
function fetchEntryList() {
return {
type: FETCH_LIST,
payload: storage.getall()
};
}
1. 只需要像创建普通action一样创建异步action,不同的是这里action的preload字段的值是一个promise。
2. 当发现这一点,promiseMiddleware中间件会在当时,以及promise被resolve/reject时,分别创建FETCH_LIST_PENDING、FETCH_LIST_FULLFILLED、FETCH_LIST_REJECTED的action。
3. (注意到,这是对thunkMiddleware的升级:thunkMiddleware允许actionCreator返回一个函数,再将该函数传给dispatch;promiseMiddleware还是返回一个普通js对象,但是除了type字段,还有一个字段preload,该字段的值是一个promise对象。)
4. 只需要再reducer中响应promiseMiddleware创建的action即可:
//reducers/entries/list.js
import * as ActionTypes from 'actions';
const initialState = {
isFetching: false,
data: [],
};
const { pendingOf, fulfilledOf } = ActionTypes;
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case pendingOf(ActionTypes.FETCH_ENTRY_LIST):
return {
...state,
isFetching: true,
};
case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST):
return {
...state,
isFetching: false,
data: payload,
};
default:
return state;
}
}
由上可见,只要异步操作结果可以使用promise表示时redux-promise-middleware(代码中要写:promiseMiddleware)都可以大大简化actionCreator实现。
Redux的Provider和connect
【作用!!!】增强原有的组件树,使原有组件树的所有节点都拥有接触到store的能力:能够监听、读取状态数据并触发action。
// create root component based on component Deskmark
const App = connect(
state => ({ state }),
dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
})
)(Deskmark);
// create DOM container
const container = document.body.appendChild(
document.createElement('div')
);
// render root conponent with store to DOM container
render(
<Provider store={store}>
<App />
</Provider>,
container
);
react-redux:Redux官方提供的React绑定,用于在react项目中使用Redux。
react-redux提供了两个api:Provider和connect。
'Provider' : store的提供者,一般情况下,将原有组件的根节点包裹在Provider中,
这样整个组件树上的节点都可以通过connect获取store;
'connect': 用来'连接'store和组件。
connect的用法:
const App = connect(mapStateToProps, mapDispatchToProps)(counter);
1. counter称之为展示组件(即原有组件树的根节点)。
2. mapStateToProps(生成数据属性)就是从全局的状态数据中挑选、计算得到展示组件所需要数据的过程,即从state到组件属性的映射。
3. mapDispatchToProps(生成行为属性)接受参数(store.dispatch)返回一个普通的js对象,对象的内容也会被合并到最终的展示组件上。
//bindActionCreators的作用
dispatch => ({
actions: bindActionCreators(actionCreators, dispatch)
})
// connect()的第二个参数,是生成行为属性,该参数,接受actionCreators和dispatch,对每一个actionCreator绑定dispatch(使得一旦有了action,随机被dispatch出去),返回一个普通的js对象(即一系列的actions)。
组件的数据属性和行为属性的来源:
需要弄懂以下问题:
1. 当我们说如何使用redux的时候,说的其实是如何获取并使用store的内容(状态数据),以及创建并触发action的过程;
2. Redux中store的实现更多表现为reducer,通过reducer创建store的代码往往很少,且不包含业务逻辑;所以,讨论设计store的时候,讨论的其实是实现reducer。‘
this.props.actions.XX中this.props什么时候传入的?为什么用props?
【答】:因为用了Provider和connect,所以所有组件上均可以直接使用this.props.state或者this.props.actions。
使用形式有:
(1)生命周期函数中:
componentDidMount(){
this.props.actions.fetchEntryList();
}
(2)render()中:
render() {
const {state, actions} = this.props;
const {isEditing, selectedId} = state.editor;
const items = state.items;
<ItemEditor
onSave = {actions.saveEntry}
/>
}
store不是用createStore创建的么,为什么还需要在reducer中实现store?
【答】:rootReducers是将多个reducer组装在一起,使得各个单独的store形成一个全局唯一的整体。createStore(rootReducers)可看出创建store实际上就是利用了rootReducers。因此讨论设计store的时候,讨论的往往就是reducer的实现。
【记住】:行为属性和数据属性是分开的!意思就是不要指望actions的那些onSave()之类的方法也在state中,其实不是的,这些是在actions中,actions和state是分开的。state中全是数据属性。(原先的理解,应该更正为:actions和state都在store中!而非actions在state中,因为我把state和store一开始没搞清楚,弄混淆了)
创建异步action的再次解读
*** 第一份源码 ***
//actions/index.js
export const pendingOf = actionType => `${actionType}_PENDING`;
export const fulfilledOf = actionType => `${actionType}_FULFILLED`;
export const rejectedOf = actionType => `${actionType}_REJECTED`;
export const FETCH_ENTRY_LIST = 'FETCH_ENTRY_LIST';
export function fetchEntryList() {
return {
type: FETCH_ENTRY_LIST,
preload: storage.getAll()
};
}
解释:
1. pendingOf等三个函数为何放在actions中:这三个函数是用来生成最后需要dispatch给reducer 的action的type的。因此放在actions/index.js中符合边界清晰的原则。
2. 因为有了promiseMiddleware等中间件,dispatch的时候,type是带有后缀的type,preload是异步操作已经有了结果之后的返回值。(即:dispatch给reducer时候,type是FETCH_ENTRY_LIST__FULFILLED的样式;preload是具体的data/error)。
//reducers/items.js
import * as ActionTypes from '../actions';
const initialState = {
isFetching: false,
items: [],
};
const { pendingOf, fulfilledOf } = ActionTypes;
export default function (state = initialState, action) {
console.log('action:');
console.log(action);
const { type, payload } = action;
switch (type) {
case pendingOf(ActionTypes.FETCH_ENTRY_LIST):
return {
...state,
isFetching: true,
};
case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST):
return {
...state,
isFetching: false,
items: payload,
};
default:
return state;
}
}
//components/Deskmark/index.jsx
...
const { state, actions } = this.props;
const { items } = state.items;
...
【解释】:
1. reducer的作用就是拿着当前reducer内部有的initialState,和刚进来的action(type,preload),来决定如何更新当前reducer内部的state(是一个json对象)。
actionCreator中preload的用法:
actionCreator返回一个js对象。该action,必须包含一个type字段,鼓励将负载信息放在preload字段里面。
//第一种:
返回的是js对象,preload上可以挂载一个一次性异步操作。
export function fetchEntryList() {
return {
type: FETCH_ENTRY_LIST,
payload: storage.getAll(),
};
}
//第二种:
返回的是js对象,preload上可以挂载一个一次性异步操作。
export function fetchEntry(id) {
return {
type: FETCH_ENTRY,
payload: {
promise: storage.getEntry(id),
data: id,
},
};
}
//第三种:
返回的不是一个js对象,而是一个函数的时候,thunkMiddleware中间件会将dispatch和getState传给该函数。在该函数中决定何时dispatch一个js对象。
export function deleteEntry(id) {
const promise = storage.deleteEntry(id).then(() => id);
return dispatch => {
dispatch({
type: DELETE_ENTRY,
payload: {
promise,
data: id,
},
});
promise.then(
() => dispatch(fetchEntryList())
);
};
}
1. 当promiseMiddleware中间件检测到preload是一个promise的时候,会自动构造带有_FULLFILLED等后缀的type,并在preload中携带异步操作完成的结果。然后传递给reducer。
2.
Promise的小注意
export function deleteEntry(id) {
return getAll()
.then(
results => results.filter(
result => result.id !== id
)
)
.then(saveAll);
// 当使用了saveAll()则只要删除一个item,所有的item都被删除了。
//(解释: saveAll()立即执行,即也在getAll()这个异步操作之前,
// 但是saveAll()没有传递任何参数值进来,因此相当于强行给STORAGE_KEY赋了空值。
// 此处其实暗藏着,前一个then传递回来的results作为参数给了saveAll)
}
preload的用法
网友评论