Reducer必须为纯函数

  • 什么是纯函数? 所谓纯函数基本可以用一句话来替代,那就是”一个函数,相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。“,下面展示一下纯函数与非纯函数的区别

非纯函数

// 非纯函数(所谓的浅复制)

// 设置一个对象
var obj1 = {
name:'lam',
}

// 设置一个非纯函数(接受一个对象,并修改对象的指定属性)
function changeObj(obj){
obj.name = '肥林'//将接收形参对象的属性修改为肥林
}

changeObj(obj1) // 运行函数修改设置的对象属性

console.log(obj1);// { name: '肥林' }
/* 可以看出,函数修改了源对象中的属性(浅复制) */

纯函数

// 纯函数(所谓的深复制)

// 设置一个对象
var obj1 = {
name:'lam',
}

// 设置一个纯函数(接受一个对象,并修改对象的指定属性)
function changeObj(obj){
var newObj = {...obj}//作形参的对象复制
newObj.name = '肥林'//将接收形参对象的属性修改为肥林
}

changeObj(obj1) // 运行函数修改设置的对象属性

console.log(obj1);// { name: 'lam' }
/* 可以看出,函数并没有修改了源对象中的属性(深复制) */

因为对象是指向内存中的地址,而reducer又是修改对象的,因此我们必须使用纯函数来设置reducer,redux源代码中将oldStatenewStatereducer返回的结果)做比较,如果reducer为非纯函数,两者指向同一个地址,导致react认为state无变化,从而不更新页面。这样做是牺牲一点计算性能(生成新对象)来保证页面刷新,在页面更新时使用reactdiff算法来计算需要更新的组件。之所以这样设计,是为了避免在reducer中进行大量的深比较。

Reducer合并(combineReducers)

  • 官方文档

  • 我们在编写代码的时,有时候会遇到一个全局的store中会有多个不同的action所处理的属性之间没有联系,此时我们就可以将Reducer函数拆分成多个小的Reducer,不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。如下面的案例

案例需求

  • 上一篇博客的案例作进一步的更改, 进入About组建后,使用Redux中维护的pageName数据并展示在页面上,随后将这两个Action分开成小的Reducer在最后合并成一个大的Reducer

  • home.js(首页)

import React, { Component } from 'react'
// 引入路由相关的组件
import { HashRouter as Router, Route, Redirect, Switch, NavLink } from 'react-router-dom'
import store from '../../store/index.js'//引入全局store
// 引入对应的组件
import About from './about.js'//关于页
import Info from './info.js'//个人信息页
import Footer from './footer.js'//底部区域
import NotFound from './404.js'//404页面

export default class home extends Component {
componentDidMount(){
/*
通过store.subscribe()函数可以监听store中的数据变化,
store.subscribe()函数的参数接收一个函数, 该函数在store数据发生更新自动回调
*/

store.subscribe(()=>{
console.log('home组件监听中....');
// 修改组件中state的状态重新渲染组件
this.setState({
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
isShow:store.getState().isShowBottomReducer.isShow
})
})
}
state = {
// 我们可以在其他文件中通过 store.getState 来获取当前的state;
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
isShow:store.getState().isShowBottomReducer.isShow // 获取全局store中的状态
}
render() {
console.log('是否展示底部区域',store.getState().isShowBottomReducer.isShow);
return (
<div>
<h1>首页</h1>
<p>(去到信息页关闭底部,去到关于页开启底部)</p>
<div style={{ width: '100%', height: '400px', backgroundColor: 'yellowgreen' }}>
<Router>
<ul>
<li><NavLink to={'/home/info'}>info</NavLink></li>
<li><NavLink to={'/home/about'}>about</NavLink></li>
</ul>
{/* 使用Switch来解决每次页面刷新,重定向功能执行的bug(模糊匹配) */}
<Switch>
<Route path="/home/about" component={About} />
<Route path="/home/info" component={Info} />

<Redirect from="/" to="/home/info" exact />
{/* 匹配不到的页面则展示404 */}
<Route component={NotFound} />
</Switch>
</Router>
</div>
{/* 通过判断全局store中的isShow来执行是否展示Footer组件 */}
{this.state.isShow && <Footer />}
</div>
)
}
}

  1. info.js(个人信息页)
import React ,{useEffect}from 'react'
import store from '../../store/index.js'//引入全局store

export default function Info() {
// 模拟生命周期
useEffect(()=>{
console.log('进入info组件');
// 进入info组件修改全局store中的isShow为true(显示底部区域)

/*
修改store中的数据不能直接修改, 必须要通过dispatch来派发action;
通常action中都会有type属性,也可以携带其他的数据;
*/
// 通过action来修改state
store.dispatch({
type:'changeBottom',
isShow:true
})
return ()=>{
console.log('退出info组件');
// 离开info组件修改全局store中的isShow为false(关闭底部区域)
store.dispatch({
type:'changeBottom',
isShow:false
})
}
},[])

return (
<div>个人信息页</div>
)
}
  1. about.js(关于页面)
import React ,{useEffect,useState}from 'react'
import store from '../../store/index.js'//引入全局store

export default function About() {
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
console.log(store.getState().pageNameReducer.pageName);
// 使用hooks创建调用state
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
const [title,setTitle] = useState(store.getState().pageNameReducer.pageName)
// 模拟生命周期
useEffect(()=>{
console.log('进入About组件');
// 通过action来修改state
store.dispatch({
type:'showTitle',
payload:'关于页',//将全局store中的pagename改成关于页
})
return ()=>{
console.log('退出About组件');
// 离开About组件修改全局store中的payload为false(关闭底部区域)
}
},[])

return (
// 展示全局store中的pageName
<div>{title}</div>
)
}
  1. 小型Reducer(最后在全局的store中合并成大的)
/* 1. 是否展示底部区域的小reducer(isShowBottomReducer) */
// 创建修改store的方法(reducer) 小型最后合并成一个Reducer
const isShowBottomReducer = (prevState={
isShow:true
},action)=>{
let newState = {...prevState}// 纯函数写法
switch(action.type){
case "changeBottom"://判断修改字段
newState.isShow = action.isShow// 纯函数写法
return newState
default:
return prevState
}
}

export default isShowBottomReducer


/* 2. 展示关于页面的标题的小型reducer(pageNameReducer) */
// 创建修改store的方法(reducer) 小型最后合并成一个Reducer
const pageNameReducer = (prevState={
pageName:'unTitle'
},action)=>{
let newState = {...prevState}// 纯函数写法
switch(action.type){
case "showTitle"://判断修改字段
newState.pageName = action.payload// 纯函数写法
return newState
default:
return prevState
}
}

export default pageNameReducer
  1. store.js(全局store)
// 1. 引入redux(combineReducers用于合并 reducer)
import {combineReducers, createStore} from 'redux'

// 2. 引入其他的小Reducer
import pageNameReducer from './reducer/pageNameReducer'//处理关于页面标题的reducer
import isShowBottomReducer from './reducer/isShowBottomReducer'//处理底部区域展示的reducer

// 3. 合并reducer
const reducer = combineReducers({
pageNameReducer,// 处理关于页面标题的reducer
isShowBottomReducer// 处理底部区域展示的reducer
})

/*
由于创建的state是不能直接放到创建的store中的, 需要通过reducer将数据添加到store中,
因此创建store时必须创建reducer;reducer函数的返回值, 会作为store之后存储的state,
只要调用dispatch就会重新执行reducer函数,reducer是一个纯函数,不可以直接修改state,
*/

// 4.. 创建全局store
const store = createStore(reducer)

// 5. 导出store
export default store

结果展示:

image

image