Appearance
使用 redux 及 react-redux 进行状态管理
yarn add redux react-redux redux-thunk redux-devtools-extension
redux 核心主要是
- reducer
- action
- store
使用 react-reudx 则需要将原本的组件(UI 组件),使用容器组件(content)进行包裹。 容器组件会将 redux 中保存的状态和操作状态的方法使用 Props 传递给 UI 组件。
主要的使用流程:
- 创建 store
- 创建 action 定义操作数据的动作(action)
- 创建 reducer 包含数据(preState)和操作数据的方法(reducer)
- 合并 reducer
- 在 Ui 组件中,使用 hooks,获取数据(useSelector)和操作数据的方法(useDispatch)
增加 redux 的使用流程
- 创建 action 定义操作数据的动作(action)
- 创建 reducer 包含数据(preState)和操作数据的方法(reducer)
- 合并 reducer
store.js
下面是将 redux 写成一个 js 的示例,实际项目中不会这样操作 包含 action reducer store
jsx
// store.js
import { createStore, applyMiddleware, combineReducers } from 'redux' // combineReducers用于合并所有reducer
import thunk from 'redux-thunk' // 引入redux-thunk 用于支持异步action
// 引入redux-devtools-extension 用于使用 redux 开发工具的
import { composeWithDevTools } from 'redux-devtools-extension'
// action 专门为Count组件生成action对象
const creatIncrementAction = (data) => {
return {
type: 'increment',
data
}
}
// 同步action,返回值为object的对象
const creatDecrementAction = (data) => {
return {
type: 'decrement',
data
}
}
// 所谓的异步acton,就是action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的
const creatIncrementAsyncAction = (data, time) => {
return (dispatch, getState) => {
setTimeout(() => {
const p = getState() // 还能获取reducer 中的数据
const y = getState().getIn(['abc', 'y']) // 使用了 immutable 获取reducer中定义的数据的方法
dispatch(creatIncrementAction(data))
}, time)
}
}
// 异步获取数据模板
const getTopBannersAction = () => {
return async (dispatch) => {
const res = await getTopBannersRquest()
dispatch(setTopBannersDataAction(res.banners))
}
}
// ------------------------------------
// reducer
// 初始化状态
const initState = {
count: 0
}
// 操作状态的方法
function countReducer(preState = initState, action) {
const { data, type } = action
switch (type) {
case 'increment':
// 展开对象,并重新赋值
console.log({ ...preState })
// 后续这种方法会弃用
return { ...preState, count: preState.count + data }
// 改而使用这个方法,需要安装 immutable,
// preState.set('count',preState.count + data)
case 'decrement':
return { ...preState, count: preState.count - data }
default:
return preState
}
}
// 其他的Reducer
function otherReducer(preState, action) {
// 函数体
}
// 合并所有reducers
const allReducers = combineReducers({
Count: CountReducer,
Person: PersonReducer
})
// ----------------------------------
const store = createStore(
allReducers,
composeWithDevTools(applyMiddleware(thunk))
)
export {
store,
creatIncrementAction,
creatDecrementAction,
creatIncrementAsyncAction
}
App.jsx
下面是 使用 Provider 自动传递 store 给所有的组件
jsx
// App.jsx
import React, { PureComponent } from 'react'
import store from './redux/store'
import { Provider } from 'react-redux'
import { CountContainer } from './containers/Count'
import { PersonContainer } from './containers/Person'
export default class App extends PureComponent {
render() {
return (
<div>
<Provider store={store}>
<CountContainer />
<hr />
<PersonContainer />
</Provider>
</div>
)
}
}
UI 组件
下面是将 UI 组件和内容组件进行联系
jsx
// 此处没有引入store,是因为在App.jsx中使用 Provide 自动传递个所有需要调用的组件
import React, { createRef, PureComponent } from 'react'
import {
creatIncrementAction,
creatDecrementAction
} from '../../redux/actions/Count'
import { connect } from 'react-redux'
class CountUI extends PureComponent {
state = {
count: 0
}
selectRef = createRef()
increment = () => {
const { value } = this.selectRef.current
this.props.increment(parseInt(value))
}
decrement = () => {
const { value } = this.selectRef.current
this.props.decrement(parseInt(value))
}
incrementIfOdd = () => {
const { value } = this.selectRef.current
if (this.props.count % 2 !== 0) {
this.props.increment(parseInt(value))
}
}
incrementAsync = () => {
const { value } = this.selectRef.current
setTimeout(() => {
this.props.increment(parseInt(value))
}, 500)
}
render() {
return (
<div>
<h2>我是Count组件</h2>
<h3>Person组件的总人数为{this.props.persons.length}</h3>
<h4>{this.props.count}</h4>
<select ref={this.selectRef}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>增加</button>
<button onClick={this.decrement}>减少</button>
<button onClick={this.incrementIfOdd}>奇数增加</button>
<button onClick={this.incrementAsync}>异步增加</button>
</div>
)
}
}
const mapStateToProps = (state) => ({
count: state.Count.count,
persons: state.Person.persons
})
const mapDispatchToProps = {
increment: creatIncrementAction,
decrement: creatDecrementAction
}
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)
export { CountContainer }
重写 UI 组件
由于有了 hooks 可以不在使用 mapStateToProps 和 mapDispatchToProps 方法,以及 connect 方法使 UI 组件和容器组件进行连接,可以直接在 函数式组件 中使用 hooks 获取 redux 中的数据和操作数据的方法。例如将上面的方法进行改写
jsx
import React, { createRef, memo, useRef } from 'react'
import {
creatIncrementAction,
creatDecrementAction
} from '../../redux/actions/Count'
import { useSelector, useDispatch, shallowEqual } from 'react-redux'
export default memo(function Count() {
const selectRef = useRef()
const dispatch = useDispatch()
// 将原本的方法使用dispath()包裹后进行调用即可,不用再重新定义 mapDispatchToProps 对应的方法
// 相当于原本的 mapStateToProps 中定义的变量
const { count, persons } = useSelector((state) => {
return {
count: state.Count.count,
persons: state.Person.persons
}
// 与原本使用 connect 时只会进行浅层比较不同,默认情况下,使用 useSelector 会对里面的数据进行 深层比较,只要数据发生变化,就会重新加载页面,为了优化性能,一般情况下都需要传递第二个参数 shallowEqual ,使得 useSelector 只会进行浅层比较。
}, shallowEqual)
const increment = () => {
const { value } = selectRef.current
dispatch(creatIncrementAction(parseInt(value)))
}
const decrement = () => {
const { value } = selectRef.current
dispatch(creatDecrementAction(parseInt(value)))
}
const incrementIfOdd = () => {
const { value } = selectRef.current
if (this.props.count % 2 !== 0) {
dispatch(creatIncrementAction(parseInt(value)))
}
}
const incrementAsync = () => {
const { value } = selectRef.current
setTimeout(() => {
dispatch(creatIncrementAction(parseInt(value)))
}, 500)
}
return (
<div>
<h2>我是Count组件</h2>
<h3>Person组件的总人数为{persons.length}</h3>
<h4>{count}</h4>
<select ref={selectRef}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
<button onClick={incrementIfOdd}>奇数增加</button>
<button onClick={incrementAsync}>异步增加</button>
</div>
)
})
使用 Immutable.js 操作 redux 中的数据(reducer),优化性能
- 主要是修改 reducer 中的默认数据,后续修改数据时,使用 immutable 提供的 API 即可。 Immutable 能优化性能的原因,最大化复用原本的数据。一定程度上解决 reducer 中默认数据很多的情况,只修改一个地方,就会将所有数据进行重新修改,重新渲染所有使用了数据的页面。
安装 yarn add immutable
修改设置数据的方法
jsx// 引入 immutable import { Map } from 'immutable' const defaultState = Map({ topBanners: [] })
修改 reducer 中设置数据的方法
jsxcase 'xxxx': return preState.set('topBanners', data) default: return preState
修改组件中使用了状态时,获取数据的方法
jsx// 获取 store 中存储的数据, const { topBanners } = useSelector( (state) => ({ topBanners: state.discover.get('topBanners') }), shallowEqual )
而合并全部 reducer,则不能直接使用 immutable ,需要使用 redux-immutable ,将原本的 combineReducers 的引入替换,redux-immutable 内部会自动帮助我们进行性能的优化。
jsx- import { combineReducers } from 'redux' + import { combineReducers } from 'redux-immutable' // 容器组件 const { topBanners } = useSelector( (state) => ({ - topBanners: state.discover.get('topBanners') + topBanners: state.get('discover').get('topBanners') // 还可以简写,意思是先获取 discover ,再获取 topBanners // topBanners: state.getIn(['discover', 'topBanners']) }), shallowEqual )
Pubsub-js 的使用
安装 yarn add pubsub-js
订阅
jsx
在生命周期挂载完成后,进行订阅消息
componentDidMount() {
this.token = PubSub.subscribe('test', (msg, data) => {
console.log('List 组件收到数据', data)
this.setState(data)
})
}
// 取消订阅
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
- 发布
jsx
PubSub.publish('test', { isFirst: false, isLoading: true })