基于 Redux4。
Redux 不是 Facebook 开发的,它是一个专门用于做状态管理的 JS 库,它的作用是集中式管理应用中多个组件共享的状态。Redux 和 React 并没有直接的关系,可以将 Redux 与 React、Vue 或任何其他类似的界面库一起使用。
React 是在视图层解决了 DOM 的渲染问题(开发者编写 JSX,React 生成虚拟 DOM,最终渲染成真实 DOM),但是状态 State 依然是留给开发者自己来管理。
Redux 的使用总结:
- 存储数据:通过
createStore()
生成一个 Store,用于存储数据。虽然 Store 是存储数据的地方,但 Store 中的数据都来源于 Reducer 函数的返回,因此createStore()
要接受一个 Reducer 作为参数。- 获取数据:通过
store.getState()
即可获取到更新后的 State。- 定义更新:定义一个 Action 用于描述要更新的数据类型和内容。
- 通知更新:通过
store.dispatch(action)
派发一个 Action 给 Reducer,触发 Reducer 的自动执行。- 返回更新:Reducer 函数通过计算返回一个新的 State 保存于 Store 中。
- 监听更新:通过
store.subscribe()
即可监听 Store 中数据的变化。
安装 Redux:
执行 npm install redux
安装 Redux。
Redux 的核心理念:
Redux 有三个核心理念:Store、Action、Redux。
Store:
Store:存储数据的地方。最好整个应用只有一个 Store。
createStore()
:
createStore()
:用来生成 Store。接收 Reducer 作为其参数。
虽然 Store 是存储数据的地方,但 Store 中的数据都来源于 Reducer 函数返回的 State。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 作为参数传入
createStore()
方法。
import { createStore } from 'redux'
const store = createStore(reducer)
store.getState()
:
某个时间点的数据集合,就叫做 State,可以通过 store.getState()
获取到当前时刻的 State。
const state = store.getState()
示例:
// store.js
// 引入 createStore,用于创建 Store
const {createStore} = require("redux")
// 创建 Reducer,接收两个参数,分别是当前的 State 和本次被派发的 Action,根据被派发的 Action 返回新的 State 存储在 Store 中
function reducer(state, action) {
console.log(state, action) // 初始加载 Store 会自动调用一次 Reducer 进行初始化状态,此时 state 是 undefined,action 对象中的 type 为 @@redux/INITxxx
return {
name: 'Lee'
}
}
// 创建 Store,传入 Reducer 作为其参数
const store = createStore(reducer)
// 导出 Store
module.exports = store
// index.js
// 引入 Store,初始加载 Store 会自动调用一次 Reducer 进行初始化状态
const store = require('./store')
// 获取 Store 中的数据
console.log(store.getState()) // {name: 'Lee'}
Action:
Action:Action 就是一个普通的 JS 对象,用于描述要更新的数据类型和内容,其中 type 属性是必须的,表示 Action 的名称,其他属性可以自由设置。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
}
store.dispatch()
:
store.dispatch()
:所有数据的变化,必须通过派发(dispatch) Action 来更新。接受一个 Action 对象作为参数,将其发送出去。
store.dispatch()
方法会派发一个 Action 给 Reducer,作为 Reducer 的第二个参数,会触发 Reducer 的自动执行。
store.dispatch(action)
示例:
// store.js
// 引入 createStore,用于创建 Store
const {createStore} = require("redux")
// 创建 Reducer,接收两个参数,分别是当前的 State 和本次被派发的 Action,根据被派发的 Action 返回新的 State 存储在 Store 中
function reducer(state, action) {
console.log(state, action) // 初始加载 Store 会自动调用一次 Reducer 进行初始化状态;派发 Action,Reducer 也会被自动执行
switch (action.type) {
// 匹配到 Action,则根据 Action 返回最新的 State
case 'change_name':
return {...state, name: action.name}
// 否则返回默认值
default:
return {name: 'Lee'}
}
}
// 创建 Store,传入 Reducer 作为其参数
const store = createStore(reducer)
// 导出 Store
module.exports = store
// index.js
// 引入 Store,初始加载 Store 会自动调用一次 Reducer 进行初始化状态
const store = require('./store')
// 获取 Store 中的数据
console.log(store.getState())
// 修改 Store 中的数据
// 1. 定义 Action,用于描述要修改的数据类型和内容
const action = {
type: 'change_name',
name: 'Mary',
}
// 2. 派发 Action,Reducer 会被自动执行,Action 会被传入 Reducer 中作为第二个参数
store.dispatch(action)
// 再次获取 Store 中的数据
console.log(store.getState())
Action Creator
:
Action 如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator
。
// 定义 Action Creator,只需传入想要修改的数据即可
const changeNameAction = name => ({
type: 'chanhe_name',
name,
})
// 使用 Action Creator 生成 Action
const action = changeNameAction('Mary')
Reducer:
Reducer:Store 接收到 Action 以后,必须给出一个新的 State,这种 State 的计算过程就叫做 Reducer。Reducer 是一个纯函数,它接受当前 State 和 Action 作为参数,返回一个新的 State。
Reducer 是一个纯函数,因此不能直接修改传入的 State,而需生成一个新的 State 返回。
初次加载 Store 会自动调用一次 Reducer 进行初始化状态,此时 state 是 undefined,action 对象中的 type 为
@@redux/INITxxx
。
手动调用store.dispatch()
也会触发 Reducer 的自动执行。
一旦 Reducer 返回新的 State,Store 就会更新存储的数据。
const reducer = function (state, action) {
...
return newState
}
store.subscribe()
:
store.subscribe()
:用于设置监听函数。接收一个回调函数作为监听函数,一旦 State 发生变化,就会自动执行监听函数。返回值也是一个函数,调用返回的函数就可以解除监听。
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
unsubscribe()
示例:
// store.js
// 引入 createStore,用于创建 Store
const {createStore} = require("redux")
// 创建 Reducer,接收两个参数,分别是当前的 State 和本次被派发的 Action,根据被派发的 Action 返回新的 State 存储在 Store 中
function reducer(state, action) {
switch (action.type) {
// 匹配到 Action,则根据 Action 返回最新的 State
case 'change_name':
return {...state, name: action.name}
case 'change_age':
return {...state, age: state.age + action.age}
// 否则返回默认值
default:
return {name: 'Lee', age: 18}
}
}
// 创建 Store,传入 Reducer 作为其参数
const store = createStore(reducer)
// 导出 Store
module.exports = store
// index.js
// 引入 Store,初始加载 Store 会自动调用一次 Reducer 进行初始化状态
const store = require('./store')
// 监听 Store 中数据的变化。只要 Store 中的数据发生变化,就会自动回调监听函数
store.subscribe(() => {
console.log(store.getState())
})
// 修改 Store 中的数据
store.dispatch({
type: 'change_name',
name: 'Mary',
})
// 再次修改 Store 中的数据
store.dispatch({
type: 'change_age',
age: 2,
})
combineReducers()
:
当项目应用变得复杂,Store 必定会变成一个非常庞大的对象,而在整个应用中,有些 State 之间是互不关联的,因此可以拆分 Reducer,不同的 Reducer 处理不同的 Action 返回不同的 State。
// 不拆分的 Reducer
function countreducer(state, action) {
// 匹配到 Action,则根据 Action 返回最新的 State
switch(action.type) {
case 'change_name':
return {...state, name: action.name}
case 'change_age':
return {...state, age: state.age + action.age}
case 'increase':
return {...state, count: state.count + action.count}
case 'decrease':
return {...state, count: state.count - action.count}
// 否则返回默认值
default:
return {name: 'Lee', age: 18, count: 10}
}
}
// 拆分后的 Reducer
function userReducer(state, action) {
switch (action.type) {
// 匹配到 Action,则根据 Action 返回最新的 State
case 'change_name':
return {...state, name: action.name}
case 'change_age':
return {...state, age: state.age + action.age}
// 否则返回默认值
default:
return {name: 'Lee', age: 18}
}
}
function countReducer(state, action) {
// 匹配到 Action,则根据 Action 返回最新的 State
switch(action.type) {
case 'increase':
return {...state, count: state.count + action.count}
case 'decrease':
return {...state, count: state.count - action.count}
// 否则返回默认值
default:
return {count: 10}
}
}
combineReducers()
:合并 Reducer,把多个小的 Reducer 合并成一个大的 Reducer,每个小的 Reducer 只需负责自己管理的 State,大的 Reducers 就可以传入 createStore()
中。接收一个 key: value
形式的对象,key 值是小的 Reducer 命名,用来控制 State 中 key 的命名,value 值就是小的 Reducer。
combineReducers()
的实现原理:const combineReducers = reducers => { // 返回一个合并后的 Reducer 函数 return (state = {}, action) => { const newState = {...state} ... // 此处省略新旧 State 的对比 // 根据传入对象的 key 获取到对应的 State const reducerKeys = Object.keys(reducers) for (let i = 0 ; i < reducerKeys.length; i++) { const key = reducerKeys[i] const reducer = reducers[i] newState[key] = reducer(newState[key], action) } return newState } }
例如:
combineReducers({ reducer1: reducerFunc1, reducer2: reducerFunc2, })
那么,State 对象的结构是:
{ reducer1: ..., reducer2: ..., }
import {createStore, combineReducers} from 'redux'
// 合并拆分后的 Reducer
const reducer = combineReducers({
user: userReducer,
count: countReducer,
})
// 关联 Store 和合并后的 Reducer
const store = createStore(reducer)
// 访问 name 属性:
const name = store.getState().user.name
Redux 的三大原则:
- 单一数据源:整个应用程序的 State 被存储在一棵 object tree 中,并且这棵 object tree 只存储在一个 Store 中。单一数据源可以让整个应用程序的 State 变得方便维护、修改、追踪。
Redux 并没有强制不能创建多个 Store,但是那么做不利于数据的维护。
- State 是只读的:唯一修改 State 的方法就是触发 Action,不要试图在其他地方通过任何的方式来修改 State。这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行。
- 使用纯函数来执行修改:通过 Reducer 将旧的 State 和 Action 联系在一起,返回一个新的 State。所有的 Reducer 都应该是纯函数,不能产生任何的副作用。
在 React 项目中使用原生 Redux:
在 React 项目中使用原生 Redux 会有些繁琐,不推荐。
以下示例会发现:increase.jsx
和 decrease.jsx
中存在很多重复代码,其实可以使用高阶组件,将这部分重复代码进行抽取后,返回一个增强后的新组件。这也就是 react-redux
的实现原理。
// store/index.js
import {createStore} from 'redux'
import reducer from './reducer'
// 创建 Store 并导出
export default createStore(reducer)
// store/reducer.js
const initialState = {
count: 10,
}
// 创建 Reducer 函数,根据不同的 Action 返回不同的 State
export default (state = initialState, action) => {
switch(action.type) {
case 'increase':
return {...state, count: state.count + action.count}
case 'decrease':
return {...state, count: state.count - action.count}
default:
return state
}
}
// store/action.js
// 创建 Action 的 Action Creator
export const increaseAction = count => ({
type: 'increase',
count,
})
export const decreaseAction = count => ({
type: 'decrease',
count,
})
// coponents/increase.jsx
import React, { PureComponent } from 'react'
import store from '../store'
import {increaseAction} from '../store/action'
export default class Increase extends PureComponent {
state = {
// 通过 store.getstate() 方法来获取 Store 中数据的初始值
count: store.getState().count,
}
componentDidMount() {
// 通过 store.subscribe() 方法来监听 Store 中数据的变化
this.unsubscribe = store.subscribe(() => {
const {count} = store.getState()
this.setState({count}) // 将组件的 setState() 方法放入监听函数中,就可以实现 Store 中数据变化后组件的自动渲染
})
}
componentWillUnmount() {
// 取消监听 Store 中数据的变化
this.unsubscribe()
}
handleIncrease = () => {
// 通过 store.dispatch() 方法派发 Action 来修改 Store 中的数据
store.dispatch(increaseAction(1))
}
render() {
return (
<div>
<div>Increase Count:{this.state.count}</div>
<button onClick={this.handleIncrease}>+1</button>
</div>
)
}
}
// components/decrease.jsx
import React, { PureComponent } from 'react'
import store from '../store'
import {decreaseAction} from '../store/action'
export default class Decrease extends PureComponent {
state = {
// 通过 store.getstate() 方法来获取 Store 中数据的初始值
count: store.getState().count,
}
componentDidMount() {
// 通过 store.subscribe() 方法来监听 Store 中数据的变化
this.unsubscribe = store.subscribe(() => {
const {count} = store.getState()
this.setState({count}) // 将组件的 setState() 方法放入监听函数中,就可以实现 Store 中数据变化后组件的自动渲染。
})
}
componentWillUnmount() {
// 取消监听 Store 中数据的变化
this.unsubscribe()
}
handleDecrease = () => {
// 通过 store.dispatch() 方法派发 Action 来修改 Store 中的数据
store.dispatch(decreaseAction(1))
}
render() {
return (
<div>
<div>Increase Count:{this.state.count}</div>
<button onClick={this.handleDecrease}>-1</button>
</div>
)
}
}
// App.jsx
import React, { PureComponent } from 'react'
import Increase from './components/increase'
import Decrease from './components/decrease'
export default class App extends PureComponent {
render() {
return (
<div>
<Increase />
<Decrease />
</div>
)
}
}