@yacent
2017-11-06T00:59:00.000000Z
字数 13676
阅读 1777
React学习笔记
主要通过 Reducer,根据 action 来更新 state
reducer(state, action) => return new state
Action只是指明了有事情要发生,但并未指明如何更新state
区分 Action 和 Action创建方法
Action只是声明了 actionTypes,而 action创建方法则返回具体的调用接口
(state, action) => return new state
const app = combineReducers({
todo: todos,
visibilityFilter
})
// 那么 store.getState()应该如下
{
todo: {...},
visibilityFilter: 'all'
}
combineReducers
辅助函数的作用,是把一个由多个不同的Reducer函数作为 value的object,合并成一个最终的reducer函数,然后就可以对这个reducer调用 createStore
合并后的reducer可以调用各个子reducer,并把他们的结果合并成一个 state对象。 state对象的结构由传入的多个reducer的key决定。(即 state的结构,与 combineReducer当中的结构一样)
使用 combineReducers的好处是,可以将state当中的结构进行划分,使得每一个key值都只负责某一模块,结构更加清晰。
tips:
combineReducers
combineReducers
。并不是一定要在最外层使用,实际上,可以把一些复杂的reducer拆分成单独的孙子级reducer,甚至更多层。高阶reducer的力量
具体请看上面的链接
阅读链接
初始化 Redux app 有如下的两种方式
createStore
传入第二个参数sub-reducers
传入第一个参数的默认值但是,如果同时在自己的 reducer 和createStore当中都传入了值,那么 store的初始值应该是什么呢?
考虑下面一种最简单的情况
function counter(state = 0, action) {
switch (action.type) {
case 'INCREAMENT': return state + 1;
default: return state;
}
}
import { createStore } from 'redux'
let store = createStore(counter);
console.log(store.getState()); // 0
因为 createStore传入的值为 undefined,在 counter reducers当中,由于接收到的state为undefined,根据es6的函数默认值的调用方法,可以知道,当传入的值为undefined的时候,会使用默认值 0
如果是如下的形式
let store = createStore(counter, 42);
console.log(store.getState()); // 42
这次因为传入的是42,而不是 undefined
,所以state的值将会是 42
;
如果 createStore 当中传入了第二个参数,那么其初始值的优先级会高于 reducer自己设置的默认值
阅读链接
总的一句,createReducers当中的 key值的组合,即 state的结构
unsubscribe
因为每个 reducer当中都有 default
的处理,故可以初始化 store 的结构
1) 所有数据遵循相同的声明周期
2) redux应用中数据的声明周期,四个步骤
combineReducers
来进行实现 mapStateToProps
示例如下
mapStateToProps = (state) => {
return {
todo: state.todo
}
}
mapDispatchToProps
示例如下
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick (id) {
dispatch(toggleTodo(id))
}
}
}
通过 给组件使用connect(mapStateToProps, mapDispatchToProps),来限定了组件当中所能展示的完整的state树当中部分state
使用 provider 来包裹容器,将state向下传递
let store = createStore(todoApp)
<Provider store={store}>
<App />
</Provider>
容器组件:描述如何运行(数据的获取,状态的更新等)
展示组件:描述如何进行展示数据
使用中间件,进行异步的操作,以前的 action创建方法只能返回一个对象,而现在可以返回一个函数,由中间件对函数进行数据的处理
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}
function fetchPosts(subreddit) {
return (dispatch, getState) => {
dispatch(requestPost(subreddit))
return fetch('***')
.then(response => response.json())
.then(json => dispatch(receivePost(subreddit, json)))
}
}
// index.js
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware
)
)
在express 或者 Koa等框架当中,Middleware
是指被嵌入到框架中,接收请求到产生响应过程中的代码
而在 redux 当中,则是指,action发起之后,到达 Reducer之前的扩展部分
常用的操作如下:
如果想要将 "#" 从URL当中移除,那么需要从React Router 导入 browserHistroy
来实现
<Router history={browserHistroy}>
<Route path="/(:filter)" component={App} />
</Router>
前提:只要你不需要兼容古老的浏览器,比如IE9,你都可以使用 browserHistory
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
由于 redux当中的 state是只读的,只能通过action去改变,并且reducer的原理是 (state, action)=> new state
,返回一个新的state,无副作用,不会改变原来的state的状态。
但每次都使用 Object.assign(),会比较麻烦,可以尝试使用 ...
运算符,
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return { ...state, visibilityFilter: action.filter }
default:
return state
}
}
对象展开运算符目前仅仅在 ES7当中能够使用,需要使用Babel进行
const ADD_TODO = 'ADD_TODO';
const REMOVE_TODO = 'REMOVE_TODO';
好处:
写简单的action creator很容易让人厌烦,且会产生较多的多余样板代码,如下
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function editTodo(id, text) {
return {
type: 'EDIT_TODO',
id,
text
}
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
}
}
可以写一个用于生成 action creator 的函数
function makeActionCreator(type, ...argNames) {
return function(...args) {
let action = { type }
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]
})
return action;
}
}
const ADD_TODO = 'ADD_TODO';
makeActionCreator(ADD_TODO, 'todo')
在返回完整的html时,还需要返回初始数据
把数据发送到客户端,需要以下步骤:
在客户端,使用服务器返回的 state 创建并初始化一个全新的 Redux store。
Redux 在服务端惟一要做的事情就是,提供应用所需的初始 state。
在服务端,即用 ReactDOM.renderToString 来将html输出
常用的测试工具:
Enzyme
、React Test Utils
、浅渲染
当因为太多的衍生计算和重复渲染导致出现性能问题时,大多数的Redux项目会开始使用 Reselect
使用Redux能够轻而易举地实现撤销历史,原因如下
(state, action) => state
可以自然地实现 "reducer enhances" 或者 "higher order reducers"。它们在你为reducer添加额外的功能时保持着这个签名。追溯历史记录的state的结构,可以设计成如下形式
{
past: Array<T>,
present: T,
future: Array<T>
}
设计Reducer
const initialState = {
past: [],
present: null, // (?) 我们如何初始化当前状态?
future: []
}
function undoable(state = initialState, action) {
const { past, present, future } = state;
switch (action.type) {
case 'UNDO':
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [ present, ...future ]
}
case 'REDO':
const next = future[0]
const newFuture = future.slice(1)
return {
past: [ ...past, present ],
present: next,
future: newFuture
}
default:
// (?) 我们如何处理其他 action?
return state
}
}
但是使用Reducer忽略了三个重要的问题
present
状态?我们无法预先知道它。present
保存到 past
的工作present
的状态的控制委托给一个自定义的 reducer即 higher order function
,即接收一个Reducer 返回一个新的 Reducer。combineReducers()
也是 reducer enhancer,其为典型的例子
enhance reducer
可以用如下的方式进行展示
function doNothingWith(reducer) {
return function (state, action) {
// do something
return reducer(state, action)
}
}
修改后的 redo
和 undo
如下
function undoable(reducer) {
// 以一个空的 action 调用 reducer 来产生初始的 state
const initialState = {
past: [],
present: reducer(undefined, {}),
future: []
}
// 返回一个可以执行撤销和重做的新的reducer
return function (state = initialState, action) {
const { past, present, future } = state;
switch (action.type) {
case 'UNDO':
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [ present, ...future ]
}
case 'REDO':
const next = future[0]
const newFuture = future.slice(1)
return {
past: [ ...past, present ],
present: next,
future: newFuture
}
default:
// 将其他 action 委托给原始的 reducer 处理
const newPresent = reducer(present, action);
if (present === newPresent) {
return state
}
return {
past: [ ...past, present ],
present: newPresent,
future: []
}
}
}
}
当然,现在社区已经实现了 redux-undo
的库
即有点类似于ccs当中的前端代码库的实现,各个子应用当中的状态是相互隔离的
考虑一下这样的场景,有一个大应用,包含了很多的子应用
import React, { Component } from 'react'
import SubApp from './subapp'
class BigApp extends React.Component {
render() {
return (
<div>
<SubApp />
<SubApp />
<SubApp />
</div>
)
}
}
这些 <SubApp>
是完全独立的。它们不会共享数据或action,也互相不可见且不需要通信。
如果每个子应用之间不需要共享数据,推荐使用这种独立的方式,来进行状态的读取与写入,即每个实例都有其自己的store,它们彼此是不可见的。
深入理解 Reducer,能够明白各种 Reducer当中的实现方式,以及高级用法。
一个 Redux reducer函数需要具备
(state, action) => new state
特征的函数,函数的类型与 Array.prototype.reduce(reducer, ?initialValue)
函数类似核心概念
具体的阅读学习材料可以看 Reducer基础概念
核心概念
推荐阅读列表
核心概念
核心概念
首先要明确的是,整个应用只有一个单一的reducer函数,这个函数会被传给 createStore
的第一个参数。
一个单一的reducer最终需要做如下的几件事:
state
的值为 undefined
。reducer需要在action传入之前提供一个默认的state来处理这种情况典型的Redux reducer的基本结构
function counter(state = 0, action) {
switch (action.type) {
case 'INCREAMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
Redux 鼓励你根据要管理的数据来思考你的应用程序。数据就是你应用的 state,state 的结构和组织方式通常会称为 "shape"。在你组织 reducer 的逻辑时,state 的 shape 通常扮演一个重要的角色。
通常,state的顶层的状态树是一个普通的 JavaScript对象
大多数应用会处理多种数据类型,通常可以分为如下的三类:
Store代表着应用核心,因此应该用 域数据 和应用状态数据来定义State,而不是用UI状态。
一个典型的应用的state大致如下:
domainData1: {},
domainData2: {},
appState1: {},
appState2: {}.
ui: {
uiState1: {},
uiState2: {},
}
对于任何一个有意义的应用来说,将所有的更新逻辑都放入到单个 reducer 函数中都将会让程序变得不可维护。虽然说对于一个函数应该有多长没有准确的规定,但一般来讲,函数应该比较短,并且只做一件特定的事。因此,把很长的,同时负责很多事的代码拆分成容易理解的小片段是一个很好的编程方式。
因为 Redux reducer 也仅仅是一个函数,上面的概念也适用。你可以将 reducer 中的一些逻辑拆分出去,然后在父函数中调用这个新的函数。
主要的几个思想
combineReducers
用法使用combineReducers需要注意如下的几个限制
如上 Reducer的基础结构的内容所示
定义state结构
通常都是通过 combineReducers
来对 state进行范式化
注意使用 combineReducers的时候,最好使用 key-value的形式,自定义key的名称,然后对应不同的reducer,具有语义化!
combineReducers
进阶Redux 引入了非常实用的 combineReducers 工具函数,但我们却粗暴地将它限制于单一的应用场景:把不同片段的 state 的更新工作委托给一个特定的 reducer,以此更新由普通的 JavaScript 对象构成的 state 树
在不同的reducer当中实现共享数据主要是有如下的两种方式
redux-thunk
给action添加额外的数据
function someSpecialActionCreator() {
return (dispatch, getState) => {
const state = getState();
const dataFromB = selectImportantDataFromB(state);
dispatch({
type : "SOME_SPECIAL_ACTION",
payload : {
dataFromB
}
});
}
}
更多建议:
再次强调,Reducer 只是普通的函数,明确这一概念非常重要。combineReducers 虽然实用也只是冰山一角。
这部分是数据库表形式的设计,对于state的设计,尽量扁平化,比如一个博客的数据,不应该以每篇博客作为一个顶级层级,然后把作者、评论等相关信息嵌套进去,这样当其中某一部分发生修改时,会导致整个数据修改,会全部重新渲染,而且性能上,差很多。
应该博文相关信息为一块,评论为一块,用户为一块,然后找方法把他们连接起来。
范式化的数据包含下面几个概念:
随着应用程序的增长,在 reducer 逻辑中开始出现一些常见的模式。你可能会发现一部分 reducer 逻辑对于不同类型的数据做着相同的工作,你想通过对每种数据类型复用相同的公共逻辑来减少重复的代码。或者,你可能想要在 store 中处理某个类型的数据的多个 “实例”。
比如,想在程序中追踪多个计数器,分别命名为 A,B,和C。定义初始的 counter
reducer,然后使用 combineReducers
去设置状态。
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
}
const rootReducer = combineReducers({
counterA: counter,
counterB: counter,
counterC: counter
})
如果调用了一次 {type: 'INRECEMENT'}
,实际上,三个计数器的值都会被增加,而不是仅仅其中一个。
正如 Reducer逻辑拆分定义的那样,高阶reducer是一个接收reducer函数作为参数,并返回新的reducer函数的函数。
创建特定版本的reducer函数,每个版本只相应特定的action。
创建特定的reducer有两种最常见的方式,一个是使用给定的前缀或者后缀生成新的action常量,另一个是在action对象上附加额外的信息
第一种用的比较多,有点类似于 容器当中的 fetcherReducer 或者 queryReducer、workFlowReducer等等,都是自己生成带后缀的action常量
function createCounterWithNamedType(counterName = '') {
return function counter(state = 0, action) {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1;
case `DECREMENT_${counterName}`:
return state - 1;
default:
return state;
}
}
}
function createCounterWithNameData(counterName = '') {
return function counter(state = 0, action) {
const {name} = action;
if(name !== counterName) return state;
switch (action.type) {
case `INCREMENT`:
return state + 1;
case `DECREMENT`:
return state - 1;
default:
return state;
}
}
}
const rootReducer = combineReducers({
counterA: createCounterWithNamedType('A'),
counterB: createCounterWithNamedType('B'),
counterC: createCounterWithNamedType('C'),
});
store.dispatch({type: 'INCREMENT_B'});
console.log(store.getState());
// {counterA: 0, counterB: 1, counterC: 0}
更新嵌套数据的关键是必须适当地赋值和更新嵌套的每个级别。不要直接修改嵌套当中的数据,这样会导致更改到原来的state,导致副作用,而reducer也不是纯函数了
现在初始化State主要有两种方法
preloadState
其初始化规则,详见reducer章节,主要的就是,如果createStore当中传了参数,则优先使用其作为state的值,如果没有传,则用reducer当中给state的默认值
问题:
一般而言,如果随着时间的推移,数据处于合理的变动之中、需要一个单一的数据源、在 React 顶层组件 state 中维护所有内容的办法已经无法满足需求,这个时候就需要使用 Redux 了。
它从设计之初就不是为了编写最短、最快的代码,他是为了解决 “当有确定的状态发生改变时,数据从哪里来” 这种可预测行为的问题的。
Redux能作为任何UI层的store,只是常与 React和RN搭配使用
问题:
问题:
setState()
方法吗?强烈推荐只在store中维护普通的可序列化对象、数组以及基本数据类型(undefined除外)。
非序列化项保存在store中是可行的,但会破坏store内容持久化和恢复能力,以及会干扰时间旅行。
问题:
只推荐一个应用只有一个单一的store,不仅可以使用Redux DevTools,还能简化数据的持久化及深加工、精简订阅的逻辑处理
不能存在多个middleware链,next是将action传递给下一个middleware,而disptach则是重新开始处理。
问题:
问题:
问题:
考虑到各种性能的问题,只有维护state的范式化和扁平化,才能有效地提升性能,不要有太多的嵌套。
问题:
组件没有被重新渲染,最常见的原因,是对state进行了直接修改。
Object.assign()
或者 _.extend()
的方法复制对象,slice()
和 concat()
方法赋值数组常见的dipatch之后,数据并没有变化,很可能的原因就是因为直接修改了state,reducer不是返回一个新的state
State、Action、Reducer、dispatch、Action Creator、Midlleware、Store、Store Creator、Store enhancer
顶级暴露的方法
顶级方法的引入
import { createStore } from 'redux'
Store API
compose,从右到左来组合多个函数。 需要合成的多个函数。预计每个函数都接收一个参数。它的返回值将作为一个参数提供给它左边的函数。compose(funcA, funcB, funcC) => compose(funcA(funcB(funcC())))