学习之前先看一个简单的例子(前面文章提到过):
这个例子的需求是如下图,不管是在摄氏温度还是华氏温度都要保持对方的一致。
这里引出的问题就是,温度的输入是在子组件中,但是如果仅仅更新子组件的状态,那么相对温度的显示就会出问题。其实在前面的文章也说过,当时使用的是状态提升到上一级。子组件的变化会调用父组件的方法,更新父组件的state,然后重新render。render的同时会重新传入新的props(子组件也会重新执行render方法)。
这里的状态变化是由父组件管理的,但是手段是父组件传入对应的方法到自组件中去。目前这种需求我们可以通过父子间组件通信去完成。但是我们需要思考一下:
一是,我们假想组件之间的关系很复杂,那么我们需要传入多少的方法去实现呢?
二是,组件之间的状态变化不能很好的跟踪,意思是:假如子组件的状态变化会触发上层多个父组件的重新渲染,我不能很好的了解到组件之间状态变化的关系,必须去通过每一层级向上查找。但是我们的最终目的是什么?可能只是传动一个值,中间过度的多个层级我们不需要去关系。这就引出是不是可以有一种机制将state状态变化变得可控制,简单。
这里我思考了一个问题? 现在我们所说的state tree和组件自己的state真的有很大关系吗?
我的理解是:在组件本身state没有其他组件交互的情况下,那么我还需要使用redux吗?我的想法是不用,那么react-redux的意义是什么呢?我认为是一种解决组件交互时的数据传送的问题(映射上面的问题)。所以我们在考虑state tree(使用react-redux的情况下称为state tree)不要非把state(组建自身的state)给屏蔽掉,就将state tree看做是一种第三方用于组件数据通信的工具。
ok,我们上面对当前的react-redux的定性有了一个大概的印象。然后现在我们需要思考如何在当前组件上去实现啊这个功能。
首先是当前我们使用的一切state数据都是在组件中,但是state tree是一个脱离当前项目的第三方工具。我们的大问题是如何将组件与react-redux联系起来。
我们想象一下使用jquery的时候如何使用的,jquer对象是一个全局的对象,当我们使用的时候就是直接使用$ / Jquery对象去使用了。同样的组件与state tree的时候也是一样:
get data: 直接从state tree中拿出数据。
set data: 就是执行state tree暴露出来的方法去进行数据操作。
现在我们可以假想如下图,组件与state tree的交互是通过一些其他的东西提供的,只是目前我们还不知道。
为什么会以有这种问题呢?因为js中像这种稍微复杂的操作基本上都是一种闭包的形式去构成复杂的对象,然后暴露出允许的API去进行数据操作。后面会有例子,详情可以查看步骤(5)的源码解读。
相信大家看过一些react-redux的文档,都知道核心概念是action & reducer。这二者怎么理解呢?
Action 就是一个普通 JavaScript 对象,用来描述发生了什么
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
我的理解是: 当前state tree是一种给与系统自己去定义操作的第三方工具。因为系统不知道你的数据是什么?不知道你的数据会有什么样的操作去进行数据操作。说到这里,你应该想到了吧,就是当前state tree想给与一个系统提供服务,但是具体当前系统的服务有什么,它是不知道的。所以他定义了两个概念。Action定义操作名称,reducer定义了Action的操作会有什么样的数据操作。
reducer是在系统初始化的时候就已经缓存到了state tree中去的。但是Action的使用是后期灵活使用的,例如定义点击事件去更新state,那么这个Action就只有当用户点击了才会去触发。我为什么这这样说呢?因为系统中真正操作的时候是使用暴露出来的API进行操作的。举例
dispatch(addTodo(input.value))
这里是更新state的一个操作,dispatch是暴露出来的API,addToDo是一个Action。这里没有直接去操作reducer,因为当我们执行这行代码的时候是执行state tree的内部逻辑已经缓存的Reducers遍历去更新的,这里的内容下面的源码解析有谈到。不了解可以先跳过看下面的内容。
现在我们的图为下面:
这里还有一点先着重说明一下,对于reducer有一些规定:
不要修改state。
在default情况下返回旧的state。
react-redux为了将概念分清楚,定义了两种组件概念:
展示组件
它们只是普通的 React 组件,所以不会详细解释。我们会使用函数式无状态组件除非需要本地 state 或生命周期函数的场景。这并不是说展示组件必须是函数 -- 只是因为这样做容易些。如果你需要使用本地 state,生命周期方法,或者性能优化,可以将它们转成 class。
容器组件
现在来创建一些容器组件把这些展示组件和 Redux 关联起来。技术上讲,容器组件就是使用 store.subscribe() 从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。你可以手工来开发容器组件,但建议使用 React Redux 库的 connect() 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。
可能你看着上面的描述会有点复杂,我的理解是:如果你的组件不需要使用Redux,那么当前组件就不用创建为容器组件,但是如果需要使用Redux,那么当前的组件需要使用容器组件去封装。然后使用容器组建代替展示组件进行使用。那么如何封装呢,就是使用connect()进行封装。封装的同时传入下面两个参数:
mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。这里的含义是传入想要传入的state tree数据的子集。因为展示组件就是想要这个数据才使用容器组件的啊。
mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。这里的含义是传入方法可以用来操作state tree数据的方法。因为涉及到state tree的数据操作,所以这种方法只有传进去了。
现在我们的图为:
为什么会有这个问题呢? 因为当前我们前面的说明还是在应用上,但是对于当前react系统还没有识别react-redux的操作。这也是很关键的一步。
这一小节的源码解读会配合着将上面的图进行完善。例子使用的是:官方示例页面的todos示例。
下图中文件夹分别对应:Action, 展示组件,容器组件,reducer
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'
const store = createStore(rootReducer)
render(
,
document.getElementById('root')
)
这里先看一下const store = createStore(rootReducer)。这行代码是初始化的主要部分。
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
export default combineReducers({
todos,
visibilityFilter
})
这里我们的重点是需要看combineReducers函数,转到2)步骤。
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers); //获取自有属性中枚举属性的合集字符串, 这里只有我们传进的 ["todos", "visibilityFilter"]
var finalReducers = {}; //过滤后的可用 reducer
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning('No reducer provided for key "' + key + '"');
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]; // (闭包作用域) finalReducers 浅复制reducers
}
}
var finalReducerKeys = Object.keys(finalReducers); // (闭包作用域) finalReducerKeys 存储reducers的key集合
var unexpectedKeyCache = void 0; // 防止undefined被重写而出现判断不准确的情况
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {};
}
var shapeAssertionError = void 0;
try {
assertReducerShape(finalReducers); // 判断reducer是否规范,调到步骤 3)
} catch (e) {
shapeAssertionError = e;
}
return function combination() { // 返回一个function,形成闭包,给大家看一下当前闭包环境的作用域 : 如下图:
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];
if (shapeAssertionError) {
throw shapeAssertionError;
}
if (process.env.NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(_key, action);
throw new Error(errorMessage);
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
闭包环境私有作用域图:然后调到4)。注意这里返回的只是一个function
从上面的操作中可以看到的当前操作的是Reducers相关的操作,所以上面的图会接着变化:
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) { //遍历reducers 集合
var reducer = reducers[key];
var initialState = reducer(undefined, { type: ActionTypes.INIT }); //执行reducer[key]方法,判断是否有正确的返回值
if (typeof initialState === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined. If you don\'t want to set a value for this reducer, ' + 'you can use null instead of undefined.');
}
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.'); //判断随机的action reducers是否返回可控的数据。
if (typeof reducer(undefined, { type: type }) === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined, but can be null.');
}
});
}
执行之后返回2)。
export default function createStore(reducer, preloadedState, enhancer) { //因为这里的后面两个参数为undefined,所以下面对于和这两个参数的内容跳过了。具体的后面参数的意义可以参照官方文档
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer; //注意这里现在currentReducer指向传进来的参数,也就是上一步骤返回的function。
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
function getState() {
return currentState;
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action); //初始化state,执行我们传进来的步骤d返回的reducer function. 请调到5).
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
subscribe: function subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.');
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return { unsubscribe: unsubscribe };
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
}
dispatch({ type: ActionTypes.INIT }); //执行初始化state操作
return _ref2 = { //返回一个对象,此时我们再看一下当前的作用域的值如下图:
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
如下图: 红色框中的图是返回对象的闭包作用域,绿色框是暴露出来的API。这里需要注意的是变量currentState,因为这个是闭包里相对于redux的全局变量。走到这里我们就得到了store的值了。
这里需要注意的是当前返回的是一个store对象,也就是我们一直说的state tree。先介绍部分store的知识(取自官方文档)
Store 就是用来维持应用所有的 state 树 的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action。
Store 不是类。它只是有几个方法的对象。
getState()
返回应用当前的 state 树。
dispatch(action)
分发 action。这是触发 state 变化的惟一途径。
会使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数。返回值会被作为下一个 state
subscribe(listener)
添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化
replaceReducer(nextReducer)
替换 store 当前用来计算 state 的 reducer。
这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。
所以现在的图为:可以看到Reducer直接指向了Reducers闭包作用域,是因为初始化的时候执行的。dispatch指向reducers是为了后续的Action操作。这里和上面的图有一些变化,上面的中间部分有一个框API,,其实是我之前忘了修改了,这里去掉了。
5) 执行dispatch函数
从上面的代码中可以看到最重要的一步骤是:
currentState = currentReducer(currentState, action); //初始化state,执行我们传进来的步骤d返回的reducer function. 请调到5).
所以我们又要返回步骤2) 返回的function中去执行。
return function combination() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; //初始化state为[].
var action = arguments[1]; // {type:“@@redux/INIT”}
if (shapeAssertionError) {
throw shapeAssertionError;
}
if (process.env.NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
/*注意这里是遍历所有的reducer,因为一般我们的Action只会在一个reducer中识别到,并可能返回一个新值,那么对于其他的reducer,则不会有新的返回值,这也是为什么上面谈到reducer的时候默认值要返回传进去的state,因为要保证原先的state不变。*/
for (var _i = 0; _i < finalReducerKeys.length; _i++) { //遍历所有可用 reducer对象,然后执行reducer获取初始化状态,finalReducerKeys 为可用 reducer 的 key 值数组
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key]; //通过 key 值找到对应的 reducer
var previousStateForKey = state[_key]; //获取当前reducer的 state,作为下行代码执行reducer函数的 initialState
var nextStateForKey = reducer(previousStateForKey, action); //获取初始化状态
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(_key, action);
throw new Error(errorMessage);
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey; // 判断状态是否改变,返回对象的状态
}
return hasChanged ? nextState : state; //存在修改就返回新的state,如果没有修改就返回原先的state。
};
我们还是先看一下当前的返回值:
可能你会对这个值感到迷惑,为什么是这个值,我们看一下对应两个reducer的代码,请注意当前的state是{},因为现在执行的代码时初始化state的代码,之前的state还是undefined。
//todos
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
export default todos
因为我们的todos中如果传进来的state为undefined的情况下会默认设置为[],然后穿件来的action没有匹配成功,发挥的值就是[].
//visibilityFilters
import { VisibilityFilters } from '../actions'
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
export default visibilityFilter
同样我们看到默认值为VisibilityFilters.SHOW_ALL,这也是为什么上面返回值是那样的了。
c步骤走完之后,我们得到了store对象和一些用于操作state的API。接着就是要渲染容器组件了,这也是很重要的一步骤。以VisibleTodoList.js为例:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { VisibilityFilters } from '../actions'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t => t.completed)
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
const mapStateToProps = state => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter)
})
const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
首先说明上面的组件加载在步骤c之前加载,为什么呢?
[mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。
从上面的文件可以看出组件会监听Redux store,这里组件先加载,是为了在C步骤初始化state的时候,执行mapStateToProps将todolist的数据传给展示组件。
这里以AddToDo.js为例:
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
const AddTodo = ({ dispatch }) => {
let input
return (
)
}
export default connect()(AddTodo)
首先要先明确上面的d,这里当state初始化的时候会开始渲染展示组件,这个为什么我就不知道了,我是通过debug代码发现的,初始化成功之后const AddTodo = ({ dispatch }) 在执行的时候,可以访问到store对象暴露出来的API。
现在的图示为:
写在最后:本人只是一个初学者,如果错误望指正,React-redux中还有很多的内容需要去学习,本篇文章只是为了对于React-redux有着大体的概念。后面继续跟进吧。