全局状态管理
Redux
Redux的设计思想:
Web 应用是一个状态机,视图与状态是一一对应的。
所有的状态,保存在一个对象里面(唯一数据源)。
Redux的使用的三大原则:
Single Source of Truth(唯一的数据源)
State is read-only(状态是只读的)
Changes are made with pure function(数据的改变必须通过纯函数完成)
Redux的使用
新建store.js用于创建store全局状态容器
// store.js
import { createStore } from 'redux'
// 在redux当中 creatStore 传入一个函数 这个函数被称为reducer
// 每一次reducer执行 返回值就是最新的全局store
const store = createStore((state,actions) => {
// state 是上一次返回的state的值
// actions 就是dispath当中传递的参数
return {
a:1,
b:2
}
})
export default store
导出store后 其他组件引入即可以共享状态
// app.js
import store from './store' // 引入store 全局状态
export default function app () {
return (
<div>
<h1>app根组件</h1>
<hr />
<Test></Test>
</div>
)
}
通过store的api来进行订阅发布
store.getState() 获取store中的状态对象
store.subscribe() 订阅 接收一个函数作为参数 当dispatch触发时执行接收的回调函数 类似事件绑定 因此需要解绑
store.subscribe() 的返回值为一个函数 这个函数用于解绑订阅 在需要解绑时调用
store.dispatch() 发布 接收一个对象作为参数 这个参数中必须有type条目 会触发store中的reducer 并将对象传入reducer的action中
对于redux而言 如果一个函数最终返回的结果是一个action格式的内容 那么这个函数可以称之为 action creator 也成为action创造函数
Redux的Reducer合并
import { combineReducers, createStore } from 'redux'
// reducer函数1
const reducer1 = (state={ a:1, b:2 },actions) => {
return state
}
// reducer函数2
const reducer2 = (state={ a:1, b:2 },actions) => {
return state
}
// 通过combineReducers将多个reducer函数合并为一个reducer
const reducer = combineReducers({
reducer1,
reducer2
})
// 当访问属性时需要props.reducer1.a来获取
const store = createStore(reducer)
export default store
React-redux
创建全局状态管理
// app.js
import { Provider } from 'react-redux' // 引入Provider组件用于向组件树传递数据
import store from './store'
export default function app () {
return (
<Provider store={store}> // 将创建好的store全局状态传入
<div>
<h1>app根组件</h1>
<hr />
<Test></Test>
</div>
</Provider>
)
}
类组件
同样可以用于函数组件 (推荐)
// test2.js 类组件中
import { Component } from 'react'
import { connect } from 'react-redux' // 引入 connect 方法
class Test2 extends Component {
render () {
console.log(this.props);
return (
<div>
<h1>Test2组件</h1>
</div>
)
}
}
const map = (state) => {
// return state
return { // return 的对象就是Test2接收到的对象
a:state.a
}
}
// 通过 connect 可以把store里面返回的状态全部挂载到组件的 props 属性上面
export default connect(map)(Test2)
通过connect高阶组件封装的子组件会将connect的参数传递给子组件
类组件的dispatch
// test2.js 类组件中
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Test2 extends Component {
//-----------------dispatch方式1 不推荐---------------------------------
fn = () => {
this.props.dispatch({ // 可以通过dispatch来分发一个 actions
a: 1, // dispatch实际上就是让 store 里面的 reducer 执行
type: 'asd' // 传入的参数就是 actions
// 传入参数时必须携带type (可以在store.js中根据传入的actions中的type进行业务逻辑处理)
})
}
render () {
return (
<div>
<button onClick={this.fn}>dispatch</button> {/* 方式1的调用 */}
<button onClick={this.props.fn1}>dispatch</button> {/* 方式2的调用 */}
<h1>Test2组件</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
// return state
return {
a: state.a
}
}
//-----------------dispatch方式2 推荐---------------------------------
// 在高阶组件connect封装组件后会将属性传递给子组件 也会自动分发action 所以不需要自己调用dispatch
const mapDispatchToProps = {
fn1(){
return { type:'cc', payload:1 } // 直接return一个action对象 在connect中传入
}
}
// 通过 connect 可以把store里面返回的状态全部挂载到组件的props属性上面
// connect调用接收两个参数 第一个是映射的属性 第二个是映射的dispatch 也可以简写为对象形式{fn1}
export default connect(mapStateToProps,mapDispatchToProps)(Test2)
//--------------------------------------------------------------------
函数组件
通过自定义hook(不推荐)
// test.js 函数组件中
import Test2 from './test2' // 嵌套的子组件
import { useSelector } from 'react-redux' // 引入 redux 中的 hook useSelector
export default function Test() {
const s = useSelector((state) => { // useSelector接收一个函数作为参数 函数return的值为变量接收到的值
// return state // state为一整个store对象 相当于返回全局状态的所有值
return {
a:state.a // 获取全局状态中的部分值
}
})
console.log(s);
return (
<div>
<h1>Test1 组件</h1>
<hr />
<Test2></Test2>
</div>
)
}
函数组件的dispatch
// test.js 函数组件中
import Test2 from './test2'
import { useSelector ,useDispatch } from 'react-redux'
export default function Test() {
const dispatch = useDispatch() // 通过引入 hook useDispatch 来调用 dispatch 方法
const fn = () => { // 然后通过dispatch来分发一个 actions
dispatch({ // 传入的参数就是 actions
c:5,
type:'cde' // 传入参数时必须携带type (可以在store.js中根据传入的actions中的type进行业务逻辑处理)
})
}
return (
<div>
<button onClick={fn}>dispatch</button>
<h1>Test1 组件</h1>
<hr />
<Test2></Test2>
</div>
)
}
注:dispatch的调用更新state 表现为 异步更新
从发送action到redux内的state更新这一过程是同步的,具体可参考同步数据流。
那么为什么在具体体现上是异步的呢,其实是由于React的setState在同步代码中是异步的。
可以通过React的Devtools观察到,通过react-redux连接器封装后的组件,其实就是在组件外层包了一层高阶组件。
而这一个高阶组件在redux里的state更新时会调用setState。所以,整个过程就会呈现异步的形式。
Redux中间件
在redux中,action仅仅是携带了数据的普通js对象,action creator返回的这个action类型的对象
然后通过store.dispath()进行分发。同步的情况下一切都很完美,但是reducer无法处理异步的情况
这时就需要在action与reducer中应用一个中间件,middleware
redux-thunk
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const reducer = (state = { a: 1, b: 2 }, actions) => {
return state
}
const store = createStore(reducer, applyMiddleware( thunk ))
export default store
应用了redux-thunk中间件后 action可以为一个函数
fn1 = () => {
return () => {
store.dispatch({ type: 'sb' }) // 可以发送ajax请求得到数据后dispatch数据
}
}
// 调用
store.dispatch(this.fn1())
redux-promise
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import promise from 'redux-promise'
const reducer = (state = { a: 1, b: 2 }, actions) => {
return state
}
const store = createStore(reducer, applyMiddleware( thunk, promise ))
export default store
应用了redux-promise中间件后 action可以为一个promise
// 通过async await进行优化
fn1 = async () => {
const res = await Promise.resolve(1).then((reslove) => {
return {res:reslove,type:'cc'}
})
return res
}
// 调用
store.dispatch(await this.fn1())
Redux持久化
当需要将redux中的数据持久化到localStorage中时,需要用到redux-persist
// store.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // 如果应用最后通过cordova或uniapp封装在app优先考虑localStorage
import sessionStorage from 'redux-persist/es/storage/session' // 如果是微信公众号或web(h5)应用 一般使用sessionStorage
const persistConfig = { // 持久化配置
key:'cc', // key为存入内存中数据的键
storage, // 存入localStorage
// blacklist:['b'], 黑名单 判断哪些值不需存入内存中
// whitelist:['b'], 白名单 判断哪些值需要存入内存中 黑名单和白名单不能同时使用
}
// 定义的reducer函数
const reducer = (state = { a: 1, b: 2 }, actions) => {
return state
}
const persistedReducer = persistReducer( persistConfig, reducer ) // 使用持久化配置来持久化reducer
const store = createStore(persistedReducer) // 使用持久化的reducer来创建store
const persistor = persistStore(store) // 使用持久化的store来创建持久化存储
export { store, persistor } // 将持久化store和持久化存储进行暴露
// app.js
// 用PersistGate组件将持久化存储传入组件树中 以及持久化store
import ReactDOM from 'react-dom';
import App from './components/app.js'
import { store, persistor } from './components/store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/es/integration/react'
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App></App>
</PersistGate>
</Provider>,
document.getElementById('root')
);
修改后store全局状态会同步存储到localStorage中
Redux调试工具
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
const reducer = (state={ a:1, b:2 },actions) => {
return state
}
const store = createStore(reducer,composeWithDevTools())
export default store
使用:
import { combineReducers, createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import promise from 'redux-promise'
import { composeWithDevTools } from 'redux-devtools-extension'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' //defaults to localStorage for web
import sessionStorage from 'redux-persist/es/storage/session'
const persistConfig = { // 持久化配置
key:'cc', // key为存入localStorage中数据的键
storage, // 存入localStorage
// blacklist:['b'], // 黑名单
// whitelist:['b'], // 白名单
}
// 定义的reducer函数
const reducer1 = (state={ a:1, b:2 },actions) => {
return state
}
const reducer2 = (state={ a:1, b:2 },actions) => {
return state
}
const reducer = combineReducers({
reducer1,
reducer2
})
const persistedReducer = persistReducer( persistConfig, reducer ) // 使用持久化配置来持久化reducer
const store = createStore(persistedReducer,composeWithDevTools(applyMiddleware( thunk, promise )))
// 使用持久化的reducer来创建store 并使用调试工具 以及中间件thunk和promise
const persistor = persistStore(store) // 使用持久化的store来创建持久化存储
export { store, persistor } // 将持久化store和持久化存储进行暴露
import ReactDOM from 'react-dom';
import App from './components/app.js'
import { store, persistor } from './components/store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/es/integration/react'
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App></App>
</PersistGate>
</Provider>,
document.getElementById('root')
);