工具链
redux
实现 Redux 的源码主要包括以下几个步骤:
- 实现 createStore 函数,创建 store 对象,该函数接收一个 reducer 函数作为参数,返回一个对象。
- 在 createStore 函数内部,定义一个 state 变量来存储当前的状态值,定义一个 listeners 数组来存储所有的监听函数。
- 实现 getState 方法,返回当前的状态值。
- 实现 dispatch 方法,接收一个 action 对象作为参数,将当前的状态值和 action 对象传给 reducer 函数,更新状态值。然后遍历 listeners 数组,调用所有的监听函数。
- 实现 subscribe 方法,接收一个监听函数作为参数,将该函数添加到 listeners 数组中,以便在状态更新时调用。
- 实现 combineReducers 函数,将多个 reducer 函数合并成一个 reducer 函数。
- 在 createStore 函数内部,将传入的 reducer 函数或者合并后的 reducer 函数赋值给一个内部的 currentReducer 变量。
- 在 dispatch 方法内部,将 currentReducer 赋值给一个局部变量,以保证在 reducer 函数中调用 dispatch 方法时可以获取到最新的 reducer 函数。
下面是代码实现:
// 实现 createStore 函数
function createStore (reducer) {
let state
const listeners = []
function getState () {
return state
}
function dispatch (action) {
state = reducer(state, action)
for (let i = 0; i < listeners.length; i++) {
listeners[i]()
}
}
function subscribe (listener) {
listeners.push(listener)
}
dispatch({})
return {
getState,
dispatch,
subscribe
}
}
// 实现 combineReducers 函数
function combineReducers (reducers) {
return function (state = {}, action) {
const nextState = {}
for (const key in reducers) {
nextState[key] = reducers[key](state[key], action)
}
return nextState
}
}
在使用时,可以先定义 reducer 函数:
function 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
}
}
function visibilityFilter (state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
然后将它们传入 combineReducers 函数,创建一个 reducer 函数:
const reducer = combineReducers({
todos,
visibilityFilter
})
最后调用 createStore 函数,创建一个 store 对象:
const store = createStore(reducer)
现在就可以使用 store 对象来获取状态值、派发 action、监听状态变化了。
可以参考文档: 资料
Redux 和 React-Redux 是两个独立的库,但它们通常一起使用,因为 Redux 库本身并不针对 React,而是一个通用的状态管理库,而 React-Redux 则是一个用于将 Redux 集成到 React 应用程序中的库。
React-Redux 提供了一组 React 组件和钩子,使得在 React 应用程序中使用 Redux 变得更加容易。它提供了一个 Provider
组件,可以将 Redux store 注入到整个 React 应用程序中,并且可以使用 connect
函数将组件连接到 Redux store,使它们可以访问和修改 store 中的数据。
使用 React-Redux,我们可以将 Redux 的状态管理能力与 React 的组件化能力结合起来,使得应用程序的状态可以很方便地被管理和共享,并且组件也可以方便地访问和修改应用程序的状态。
react-redux 是如何集成到 UI 的?
react-redux
提供了两个主要的组件 Provider
和 connect
,它们用于将 Redux 状态管理与 React 组件相结合。
首先,使用 Provider
组件将 Redux store 传递给整个应用程序。可以将 <Provider>
组件作为最高层的组件,这样在应用程序中的所有组件中都可以访问到 Redux store。
下一步,使用 connect
函数连接 Redux store 和组件。connect
函数是一个高阶函数,它接收两个参数:mapStateToProps
和 mapDispatchToProps
,并返回另一个函数,这个函数接受一个组件作为参数,并返回一个增强版的组件。
mapStateToProps
函数用于从 Redux store 中获取需要的 state 数据,并将其映射到组件的 props 上。mapDispatchToProps
函数用于将 action creator 映射到组件的 props 上,这样组件就可以直接调用 action creator 发起 action,而不需要手动分发 dispatch。
使用 connect
函数生成的增强版组件可以访问到 Redux store 中的 state 和 dispatch,并将它们作为 props 传递给原始组件。在组件中,可以直接使用这些 props 来获取和更新 state,以及发起 action。当组件中的 state 或 props 发生变化时,connect
函数会自动更新组件,以反映最新的 state 和 props。
通过这种方式,react-redux
让我们可以在 React 组件中使用 Redux 进行状态管理,实现了 Redux 和 React 的无缝集成。
简单写一下更新 UI 核心代码实现
react-redux 是基于 React 和 Redux 的,主要用于将 Redux 的状态管理功能集成到 React 应用程序中。它主要包括两个部分:Provider 和 connect。
Provider 组件是 react-redux 的核心,它将 Redux 的 store 作为 props 传递给 React 组件,并通过 React 的上下文(Context)使得后代组件能够访问到 store。
connect 函数用于连接 React 组件与 Redux store,返回一个新的组件。该函数的主要作用是在组件中提供 mapStateToProps 和 mapDispatchToProps 函数,从而使组件能够从 Redux store 中读取数据,并向 store 分发 action。
下面是一个简单的实现,用于说明 react-redux 是如何集成到 UI 的:
// Provider.js
import React from 'react'
import PropTypes from 'prop-types'
export const StoreContext = React.createContext()
export default function Provider (props) {
const { store, children } = props
return (
<StoreContext.Provider value={store}>
{children}
</StoreContext.Provider>
)
}
Provider.propTypes = {
store: PropTypes.object.isRequired,
children: PropTypes.any
}
import React from 'react';
import PropTypes from 'prop-types';
import { StoreContext } from './Provider';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends React.Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(this.handleChange.bind(this));
}
componentWillUnmount() {
this.unsubscribe();
}
handleChange() {
this.forceUpdate();
}
render() {
const { store } = this.context;
const props = {
...this.props,
...mapStateToProps(store.getState(), this.props),
...mapDispatchToProps(store.dispatch, this.props),
};
return <WrappedComponent {...props} />;
}
}
Connect.contextType = StoreContext;
Connect.propTypes = {
store: PropTypes.object,
};
Connect.displayName = `Connect(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return Connect;
};
}
这里的 Provider 组件用于将 Redux 的 store 传递给 React 组件:
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './App'
import rootReducer from './reducers'
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
而 connect 函数用于将 React 组件连接到 Redux store:
import React from 'react'
import { connect } from 'react-redux'
import { increment } from './actions'
function Counter (props) {
const { count, increment } = props
return (
<div>
<p>Count: {count}</p>
<button onClick={() => increment()}>+</button>
</div>
)
}
const mapStateToProps = state => {
return {
count: state.count
}
}
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch(increment())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
Redux 的存储过程可以简单地分为以下几个步骤:
- Action Creator 函数被调用,生成一个 Action 对象;
- Action 对象被传递给 Store.dispatch() 方法;
- Redux Store 调用 Reducer 函数,将当前的 State 和 Action 作为参数传入;
- Reducer 函数根据 Action 的类型,生成一个新的 State;
- Redux Store 将新的 State 存储下来,用于下一次的状态更新;
- 组件通过调用 Store.subscribe() 方法,监听 Store 中 State 的变化;
- 当 State 发生变化时,Store 会通知所有的订阅者,订阅者会重新渲染相应的组件。
这个过程可以简单地描述为:Action -> Reducer -> Store -> View。其中,Action 是一个纯对象,它描述了发生的事件;Reducer 是一个纯函数,它接收当前的 State 和 Action,返回一个新的 State;Store 是将 Action 和 Reducer 结合起来的对象,它维护了应用程序的 State;View 则是 React 组件,它通过 Store.subscribe() 方法监听 State 的变化,根据 State 的变化重新渲染页面。
Redux 的 reducer 是纯函数,它的作用是接收一个旧的状态和一个操作,返回一个新的状态,是一个纯粹的状态转换函数,因此在 reducer 中不能执行异步操作,否则会破坏 reducer 的纯函数特性。如果在 reducer 中执行异步操作,会导致 reducer 不可预测和不可重现,因为异步操作的结果是不确定的,而 reducer 必须保证在相同的输入条件下,产生相同的输出结果。同时,在 reducer 中执行异步操作可能会导致应用的状态不一致或者有延迟的问题。
为了解决这个问题,Redux 提供了中间件的机制,比如 redux-thunk
、redux-saga
等,可以在中间件中进行异步操作,然后再将异步操作的结果传递给 reducer 进行状态更新。这样就可以避免在 reducer 中执行异步操作,保证 reducer 的纯函数特性,同时也可以完成异步操作的需求。
参考文章:http://www.cnblogs.com/MuYunyun/p/6530715.html
1.1、什么情况需要用redux?
-
用户的使用方式复杂
-
不同身份的用户有不同的使用方式(比如普通用户和管理员)
-
多个用户之间可以协作
-
与服务器大量交互,或者使用了WebSocket
-
View要从多个来源获取数据
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。多交互、多数据源场景就比较适合使用Redux。
1.2、设计思想
-
Web 应用是一个状态机,视图与状态是一一对应的。
-
所有的状态,保存在一个对象里面。
1.3、Redux工作流程
首先,用户发出 Action。
store.dispatch(action);
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有变化,Store 就会调用监听函数。
// 设置监听函数
store.subscribe(listener);
listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner () {
const newState = store.getState()
component.setState(newState)
}
如果现在没理解以上流程,不要急,看完以下API就差不多能懂得Redux的核心机制了。
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
Redux 提供createStore这个函数,用来生成 Store。 下面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。
import { createStore } from 'redux'
const store = createStore(fn)
State
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。 当前时刻的 State,可以通过store.getState()拿到。
import { createStore } from 'redux'
const store = createStore(fn)
const state = store.getState()
Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。
Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。 Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
}
上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux。 可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
Action Creator
View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
const ADD_TODO = '添加 TODO'
function addTodo (text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux')
store.dispatch()
store.dispatch()是 View 发出 Action 的唯一方法。
import { createStore } from 'redux'
const store = createStore(fn)
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
})
上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。 结合 Action Creator,这段代码可以改写如下。
store.dispatch(addTodo('Learn Redux'))
Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。 Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。下面是一个实际的例子
const defaultState = 0
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload
default:
return state
}
}
const state = reducer(1, {
type: 'ADD',
payload: 2
})
上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。 其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。 实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。 为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
import { createStore } from 'redux'
const store = createStore(reducer)
上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。 以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
store.subscribe()
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
import { createStore } from 'redux'
const store = createStore(reducer)
store.subscribe(listener)
显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。 store.subscribe方法返回一个函数,调用这个函数就可以解除监听。
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
unsubscribe()
一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。 怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加? (1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。 (2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。 (3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。 想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。
中间件的用法
本文不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。
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()的功能增强。
这里有两点需要注意: (1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
)
(2)中间件的次序有讲究。
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
)
上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。
理解了中间件以后,就可以处理异步操作了。
同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。
- 操作发起时的 Action
- 操作成功时的 Action
- 操作失败时的 Action
以向服务器取出数据为例,三种 Action 可以有两种不同的写法。
// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。
const state = {
// ...
isFetching: true,
didInvalidate: true,
lastUpdated: 'xxxxxxx'
}
上面代码中,State 的属性isFetching表示是否在抓取数据。didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。
现在,整个异步操作的思路就很清楚了。
- 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
- 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染
总结
在异步请求的时候,其实很多时候都是直接发出请求如果请求成功了之后,在存入reducers, 并不是不管成功与否,都存入reducers。
redux-thunk中间件
异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢? 奥妙就在 Action Creator 之中。
class AsyncApp extends Component {
componentDidMount () {
const { dispatch, selectedPost } = this.props
dispatch(getApplyList(selectedPost))
}
}
// ...
上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)。 这里的fetchPosts就是 Action Creator。 下面就是getApplyList的代码,关键之处就在里面, 这是我在公司的代码风格写法。
export function getApplyList (query) {
return function (dispatch) {
dispatch(modalUpdate({
loadingTable: true
}))
fetch('apply', query)
.then(function (res) {
dispatch(updateApply(res.data)) // 这个是调用的action Mppper
dispatch(modalUpdate({
loadingTable: false
}))
}).catch(function (err) {
dispatch(modalUpdate({
pageWarn: err.message,
loadingTable: false
}))
})
}
}
// 对应的action Mapper
export function updateApply (data) {
return {
type: UPDATE_APPLY,
data
}
}
这里是博客文章的代码风格写法
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())
)
上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。 这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。 拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。
上面代码中,有几个地方需要注意。
- (1)fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。
- (2)返回的函数的参数是dispatch和getState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。
- (3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。
- (4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。
这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。 而store.dispatch方法正常情况下,参数只能是对象,不能是函数。 这时,就要使用中间件redux-thunk。
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducers'
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
)
上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。 因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch。
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。 这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。
本人项目中使用的最多的就是 react-redux;
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI组件
UI 组件有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
下面就是一个 UI 组件的例子。
const Title = value => <h1>{value}</h1>
因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。
容器组件
容器组件的特征恰恰相反。
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
你可能会问,如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。 React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。 connect方法的完整 API 如下。下面这个例子是我在项目中使用的一个完整结构示例
import React, { Component } from 'react'
import { push } from 'react-router-redux'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Button, message } from 'antd'
// mapStateToProps
function propMap (state, ownProps) {
return {
modal: state.modal,
routing: ownProps
}
}
class InvoiceList extends Component {
constructor () {
super()
this.state = {
invoiceListData: {}
}
this.handleGetList = this.handleGetList.bind(this)
}
componentDidMount () {
// 每次刷新空拉数据一次
this.handleGetList()
}
render () {
const { routing, modal } = this.props
return (
<div className="app-reimbursement-invoice-list">
<ReimbursementHeaderNav current="invoice-list"/>
{/* ....... */}
</div>
)
}
// 点击查询数据
handleGetList (filters, type) {
console.log('点击查询数据')
}
}
InvoiceList.propTypes = {
routing: PropTypes.object.isRequired,
modal: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
}
export default connect(propMap)(InvoiceList)
InvoiceList就是由 React-Redux 通过connect方法自动生成的容器组件。 connect方法接受两个参数:mapStateToProps和mapDispatchToProps。 它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。 通常我们只使用了第一个参数;
mapStateToProps
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。 作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数, 后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。 下面就是getVisibleTodos的一个例子,用来算出todos。
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。 mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。 connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。 也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
})
}
}
}
从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。
如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator , 返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
// eslint-disable-next-line
filter: filter
}
}
总结,实际上项目开发过程中, 只用得上第一个参数,第二个参数一般来说是封装在reducers 层次里面的。不建议直接放置在组建成此调用。因为会导致使用和数据上的紊乱。
<Provider>
组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。React-Redux 提供Provider组件,可以让容器组件拿到state。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
const store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
React-Router路由库
使用React-Router的项目,与其他项目没有不同之处,也是使用Provider在Router外面包一层,毕竟Provider的唯一功能就是传入store对象。
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
)
redux reducer
Redux 的 reducer 是纯函数,它的作用是接收一个旧的状态和一个操作,返回一个新的状态,是一个纯粹的状态转换函数,因此在 reducer 中不能执行异步操作,否则会破坏 reducer 的纯函数特性。如果在 reducer 中执行异步操作,会导致 reducer 不可预测和不可重现,因为异步操作的结果是不确定的,而 reducer 必须保证在相同的输入条件下,产生相同的输出结果。同时,在 reducer 中执行异步操作可能会导致应用的状态不一致或者有延迟的问题。
为了解决这个问题,Redux 提供了中间件的机制,比如 redux-thunk
、redux-saga
等,可以在中间件中进行异步操作,然后再将异步操作的结果传递给 reducer 进行状态更新。这样就可以避免在 reducer 中执行异步操作,保证 reducer 的纯函数特性,同时也可以完成异步操作的需求。
redux-thunk
解redux和redux的中间件redux-thunk
ction的认识
简单点说Action就是一个对象,一个必须带key为type的对象[value是自己定义的],其他的key就根据用户自己喜好自己定义: 以下都是action的定义
1、{type:”ADD”}
2、{type:”ADD”,key1:”“,key2:”“}
educer的认识
别主观意识就是类似数组中的reduce,也不是只能定义reducer,它仅仅是一个称呼,纯函数, 函数名次自己随便定义都可以,但是函数的参数只能是state与action, 可以简单的理解为一个工厂函数,传递一个旧的state通过加工后产出一个新的state: 简单的代码如下:
function count (state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'REDUCER':
return state - 1
default:
return state
}
}
如果当state是对象的时候上面的代码是错误的: redux里面规定state是不能修改的。 在javascript中对象是引用数据类型,当你修改了state的时候,变化前后的两个state将会指向同一个地址的,react-redux就会以为这两个相同的state,因为不会执行渲染 解决办法,我们用Object.assign去处理,如有不清楚Object.assign,请参考Object.assign文档
tore的认识
store是一个全局对象,将action和reducer以及state联系在一起,主要职责: 维护应用的state 提供getState()方法获取state 提供dispatch(action)方法更新state 通过subscribe(方法)注册监听器
面三者的使用案例
'use strict'
import { createStore } from 'redux'
function count (state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'REDUCER':
return state - 1
default:
return state
}
}
const store = createStore(count)
let currentValue = store.getState()
console.log('当前的值:', currentValue)
// 定义一个监听的方法
const listener = () => {
const previosValue = currentValue
currentValue = store.getState()
console.log('上一个值:', previosValue, '当前值:', currentValue)
}
// 创建一个监听
store.subscribe(listener)
// 分发任务
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'REDUCER' })
ction创建函数
上面我们说的action是一个对象,只是含有type的key的对象 action创建函数的意思就是创建一个action的函数,函数返回一个对象
function add () {
return {
type: 'ADD'
}
}
function reducer () {
return {
type: 'REDUCER'
}
}
使用的时候直接store.dispatch(add());就可以
action创建函数的意义: action创建函数表面是返回一个对象 真正的意义在于逻辑的封装
edux-thunk中间件的认识
redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数, 函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装
function add () {
return {
type: 'ADD'
}
}
function addIfOdd () {
return (dispatch, getState) => {
const currentValue = getState()
if (currentValue % 2 === 0) {
return false
}
// 分发一个任务
dispatch(add())
}
}
考文章
mobx
mobx 与 redux 的区别
MobX 和 Redux 都是流行的 JavaScript 状态管理库,广泛用于帮助开发者以可预测的方式管理应用的状态。尽管它们都旨在解决相同的问题,但它们的设计哲学、实现方式以及提倡的最佳实践有很大差异。
设计理念的不同
- Redux:其设计理念是保持状态的可预测性,鼓励使用纯函数来执行状态变更。在 Redux 中,所有的状态变化都是通过派发(dispatching)一个动作(action)并通过一个或多个 reducer 来处理这些动作来实现的。这促进了一个单向数据流模型,使得状态管理变得清晰但也更加繁琐。
- MobX:其通过透明的函数响应式编程(TFRP)原则来实现状态管理。在 MobX 中,你可以直接修改状态,而 MobX 会负责跟踪这些状态的变化,并自动应用任何副作用(如重新渲染 UI)。这种方式使得状态管理更加直观和灵活,但可能会牺牲一些可预测性。
使用和实现上的差异
- Redux:通常需要更多的样板代码和设置。你需要定义 action 类型、action 创建函数以及 reducer。同时,它鼓励使用不可变数据模式,这可能需要使用额外的库(如 Immutable.js)。Redux 并不自带中间件和异步管理的解决方案,通常需要引入如 redux-thunk 或 redux-saga 等中间件来处理副作用。
- MobX:使用起来更简单,几乎不需要样板代码。因为 MobX 通过跟踪状态的修改并自动应用更改来工作(使用代理或装饰器跟踪属性的读写),开箱即用,而无需担心不可变性。MobX 允许使用更自然的 JavaScript 数据结构(如对象、数组),并自动为你处理检测更改和更新 UI。
性能对比
- Redux:对于大型应用,由于每次状态更新都会执行可能涉及整个状态树的深度比较,因此可能需要仔细优化选择器和避免不必要的渲染。
- MobX:由于其使用响应式系统仅跟踪实际使用的状态部分,并在这部分状态变更时才更新,通常能提供更好的运行时性能,尤其是在大型应用中。
选择哪一个?
选择 Redux 还是 MobX,很大程度上取决于项目需求、团队偏好和项目的规模。
- 如果你更喜欢函数式编程范式、需要更高程度的可预测性以及更好的时间旅行调试能力,那么 Redux 可能是更好的选择。
- 如果你倾向于面向对象的编程范式、期望更少的样板代码以及更加灵活的状态管理方式,那么 MobX 可能更符合你的需求。
两者之间的选择并不是全有或全无,实际上有些项目也会结合使用两者的优点。正确理解每种库的设计理念和适用场景是做出选择的关键。