用自定义hooks实现redux状态在纯函数上的分发

595 阅读3分钟

hooks的出现改变了传统compoent用class 书写的方式,生态对其支持还是属于适应阶段。redux状态管理在纯函数组件上的应用似乎变得棘手。虽然给出一些类似于useContext、useReducer的方式,似乎能将redux应用于这种function方式的组件,但是一探究竟后你就会发现,这些api还是很局限,不能达到redux及react-redux这一套状态管理和视图同步的效果。

下面一起来探索

1、redux、react-redux是完美方案吗?

先给出答案,是。

为什么,实现了状态的管理和共享,局部属性的改变最小化更新ui,达到最优的性能。

2、useReducer

使用useReducer 可以实现redux的状态单向数据流,但局限于纯函数内部,不能实现跨组件状态共享。

3、useContext

useContext能弥补useReducer的不足,做到全局状态的共享和redux的数据流方式。

但是,另一个问题诞生,状态改变不会引起ui的更新。

为什么,如我在根节点下面定义一个useReducer,拿到状态通过属性传给子组件,那么可以实现状态的管理和更新,劣势是每次更新都会是整个组件更新,新能极差。 如果我不绑定给子组件,通过useContext子组件自己获取,那么子组件能正常获取到全局的状态,也可以dispatch状态,但是状态的改变不能跟ui绑定。

4、我们的方案 useContext+redux+自定义hook

useContext和redux用法完全同之前的用法。我们的自定义hook做了哪些工作:

1、通过useContext获取全局状态。

2、根据调用组件的mapstatetoprops给组件设定组件自己的状态。

3、监控store状态的变化,当状态变化时,比较当前组件map的属性变化(浅比较即可),如果组件状态,会通过useState()中的改变状态函数去改变当前组件的状态。实现组件的ui重绘。

4、组件被卸载后,会去掉状态订阅。

//使用

export default function PageHead(props) {
	const mapStateToProps = (state) => {
		return {
			motoAdvert: state.motoAdvert,
		}
	}

	const mapDispatchToProps = (dispatch) => {
		return {
			motoBannerClick: ({ groupData, index }) =>
				dispatch({ type: 'click:headMotoBanner', groupData, index })

		}
	}

	const [{
		motoAdvert
	}, {
		motoBannerClick
	}] = useReactRedux(mapStateToProps, mapDispatchToProps)
	const headMotoList = get(motoAdvert, 'list', [])

	return (
		<div >
			<Flex className="main-0618-motoadvert">
				<ImgList
					noHolder
					className={`main-0618-motoadvert-banner ${headMotoList.length === 1 ? 'only-one' : ''}`}
					groupData={motoAdvert}
					limited={2}
					onClick={motoBannerClick}
				/>
			</Flex>
		</div >
	)
}
import React, { Component, useState, useEffect, useRef, useContext } from 'react'
import { storeContext } from '../js/store'

export default function useReactRedux(mapStateToProps, mapDispatchToProps) {
    const store = useContext(storeContext)  // 拿到store
    const { dispatch, subscribe, getState } = store
    // map映射
    const currentState = mapStateToProps && mapStateToProps(getState()) || {}
    const currentDispatchs = mapDispatchToProps && mapDispatchToProps(dispatch) || {}

    const [componentState, setComponentState] = useState(currentState)
    const [componentDispatchs, setComponentDispatchs] = useState(currentDispatchs)


    // 状态订阅
    useEffect(() => {
        //缓存 上次状态
        let lastComponetState
        let mySubscribe =  subscribe(() => {
            const newCompentState = mapStateToProps(getState())
            if (!isObjEqual(lastComponetState, newCompentState)) {
                setComponentState(newCompentState)
                lastComponetState = newCompentState
            }
        })
        return ()=> mySubscribe() //去掉订阅
    }, [])

    return [componentState, componentDispatchs]
}


function isObjEqual(o1, o2) {
    if (!o1 || !o2) {
        return false
    }
    var props1 = Object.getOwnPropertyNames(o1);
    var props2 = Object.getOwnPropertyNames(o2);
    if (props1.length != props2.length) {
        return false;
    }
    for (var i = 0, max = props1.length; i < max; i++) {
        var propName = props1[i];
        if (o1[propName] !== o2[propName]) {
            return false;
        }
    }
    return true;

}

我们的优势

1、 写法完全同react-redux 的connect方式。简化了接入成本和老项目翻新成本。

2、 实现了全局状态按需给到组件。实现最小化的ui更新。

3、 实现了纯函数编程的方式,避免高阶函数的嵌套地狱。

4、 甩掉了传统的class方式繁琐的生命周期,让开发变得更快乐。