React-redux 使用及实现

闾丘高峰
2023-12-01

react-redux 作用?

首先理解redux是一个独立的状态管理库,可以用到react 中,也可以用到vue中。react-redux 从名字不难看出,是用来连接react到redux。

怎么使用

react-redux 官网地址
Provider 使组件层级中的 connect() 方法都能够获得 Redux store。
connect 连接 React 组件与 Redux store。

store.js 状态仓库

// store.js
import { createStore } from "redux"
// 实现计数器功能
export function countReducer (state = 1, { type, payload }) {
	switch(type) {
		case "ADD":
			return state + payload
		case "MINUS"
			return state - payload
		default:
			return state
	}
}

const store = createStore(countReducer)
export default store

index.js 根组件

// index.js
import ReactDom form "react-dom"
import { Provider } from ""react-redux"
import Child from "./Child"
import store from "./store/"

ReactDom.render(
	// store就是状态仓库,通过Provider传递
	<Provider store={ store }>
		<Child />
	</Provider>,
	document.getElementById("root")
)

Child.js 类组件

// Child.js
import { Component } from "react"
import { connnect } from "react-redux"
import { bindActiveCreators } from "redux"

// connect 接受三个参数
// mapStateToProps 把getState映射到props
// mapDispatchToProps 把dispatch映射到props
// mergeProps 添加参数到props
export function connect(
	// mapStateToProps 是一个函数返回是一个state的状态值,可以从新命名
	(state) => {
		return { count: state.countReducer }
	},
	// mapDispatchToProps 参数是一个Object或者是一个function
	// object
	{
		add: () => { type: "ADD", payload: 1},
		minus: () => {type: "MINUS", payload: 2 }
	}, 
	// function 接收一个dispatch
	(dispatch) => {
		// 两种写法
		// 第一种所有的dispatch单独写
		const add = () => dispatch({type: "ADD", payload: 1})
		const minus = () => dispatch({type: "MINUS", payload: 2)
		
		// 第二种通过redux提供的方法bindActionCreators去合并
		let creators = {
			add: () => ({type: "ADD", payload: 1}),
			minus: () => ({type: "MINUS", payload: 2})
		}
		
		const creators = bindActiveCreators(creators, dispatch) // 参数1 creators 参数2 dispatch 
		return {
			...dispatch,
			creators
		}
	},
	// mergeProps 用于合并额外参数 函数接收三个参数 stateProps dispatchProps ownProps
	// ownProps 需要注意是组件自身的props
	(stateProps, dispatchProps, ownProps) => {
		return {
			...stateProps,
			...dispatchProps,
			...ownProps,
			...{ extra: "haha"}
		}
	}
	
)( class Child extends Component {
	redner() {
		const { count, add, minus } = this.props
		return (
			<div>{ count }</div>
			<button onClick={ add }>+</button>
			<button onClick={ minus }>-</button>
		)
	)
})

函数组件通过hookapi使用

// ChildHookPage.js
import { useSelector, useDispatch, useCallback } from "react-redux"

export function ChildHookPage() {
	const count = useSelector(({countReducer}) => countReducer)
	const dispatch = useDiaptch()
	
	const add = useCallback(() => {
		dispatch(type: "ADD", payload: 1)
	}, [])
	
	return (
		<div>{ count }</div>
		<button onClick={ add }>+</button>
	)		
}

如何实现

思路:
第一个方法Provider接收一个storechildren 然后通过context传递store
第二个方法connect是个函数(参数是mapStateToPropsmapDispatchToPropsmergeProps) 返回一个函数(参数是组件自身),然后又返回一个函数(参数是组件的props

Provider.js

// 实现Provider.js
import React from "react"

// 创建context对象
export const Context = React.createContext()

export default function Provider({store, children}) {
	// 参数中结构出store和children
	// value传递store
	return <Context.Provider value={ store }>{ children }</Context.Provider>
}

connect.js

// connect.js
import { useContext, useState, useEffect } from "react"
import { Context } from "./Provider"
import useForceUpdate from './useForceUpdate'
import bindActiveCreators from "./bindActiveCreators"
// 首先connect接受参数mapStateToProps 。。。
export default function connect(mapStateToProps, mapDispatchToProps) {
	// 第二个函数参数是组件自身
	// 第三个函数参数是组件props
	return (WrapComponent) => (props) => {
		// 先从Context获取store,因为是函数组件使用useContext
		const store = useContext(Context)
		
		// 首先mapStateToProps是一个函数,
		// 获取state执行mapStateToProps函数即可(参数是getState)
		const stateProps = mapStateToProps(store.getState())
		
		let dispatchProps
		// 判断mapDispatchToProps类型
		if (typeof mapDispatchToProps === "function") {
			// 参数是dispatch
			dispatchProps = mapDispatchToProps(store.dispatch)
		} else {
			// 如果是对象就给每个值上面添加dispatch方法 可以直接调用bindActiveCreators方法
			dispatchProps = bindActiveCreators(mapDispatchToProps, store.dispatch)
		}
		
		// 自定义hook forceUpdate
		const forceUpdate = useForceUpdate()
		// forceUpdate写到副作用里面
		useLayoutEffect(() => {
			store.subscribe(() => {
				forceUpdate()
			})
			return () => {
			}
		},[store])
		
		// 把state和dispath及自身的props返回
		return <WrapComponent {...props} {...stateProps} {...dispatchProps} />
	}
}

useForceUpdate.js

// useForceUpdate.js
import { useCallback, useReducer } from 'react'

export default function useForceUpdate() {
	// 使用hook中的forceUpdate,但是需要注意每次的值要进行改变,才会去出发forceUpdate,
	// test状态其实是没有用到,这里只是用作一个占位,主要是用forceUpdate功能
    const [test, forceUpdate] = useReducer(n => n + 1, 2)

    const update = useCallback(
        () => {
            forceUpdate()
        },
        [],
    )
    return update
}

// 函数组件更新可以用forceUpdate,在hook中的forceUpdate就是通过setState实现的
// 需要注意,如果前后两个状态值一样就不回去进行更新,所以用useReducer中每次更新的状态值不一样。

useSelector.js

// useSelector
import { Context } from "./Provider"
import { useContext } from "react"
import forceUpdate from "./useForceUpdate"
export default useSelector(selector) {
	const store = useContext(Context)
	
	const forceUpdate = useForceUpdate()
	// 这里写法同connect一致
	// 写到副作用函数中进行一个订阅
	useLayoutEffect(() => {
		const unSubscribe = store.subscribe(() => {
			forceUpdate()
		})
		return () => {
			unSubscribe()
		}
	}, [store])	

	return selector(store.getState())
}

useDispatch.js

// useDispatch
import { Context } from "./Provider"
import { useContext } from "react"

export default useDispatch() {
	// 直接返回dispatch即可
	return useContext(Context).dispatch
}

上面connect中的mapDispatchToProps中用到了bindActiveCreators方法,此方法实现也非常简单,思路:就是对所有的对象上添加dispatch方法即可,

// bindActiveCreators.js
export default function bindActiveCreators(creators, dispatch) {]
	// 两个参数,1,所有dispatch的方法对象,2,dispatch
	const obj = {}
	Object.keys(creators).forEach( n => {
		// key不变,返回一个函数添加上dispatch方法
		// 参数就是creators遍历出来函数的执行结果
		obj[n] = () => dispatch(creators[n]())
	})
	
	return obj
}

以上代码未进行测试,完整代码: https://gitee.com/uijwuy/react-ex
如果对redux还不熟悉的同学,可以看这篇redux的实现

总结:

react-redux: 相当于是react连接redux的中间件,使操作store更方便简洁connect),不需要手动组件更新的操作。了解了原理,可以自己手动封装使用。
函数组件更新: 可以用forceUpdate,但是需要主要前后两个状态值不能一致
父组件更新子组件是否更新的依据是props和引用的context的value是否改变
useEffectuseLayoutEffect区别:调用机制不同,一个异步一个同步。useEffect组建渲染之后执行,useLayoutEffect是dom变更之后渲染之前调用。

 类似资料: