React Redux环境下如何简化connect的状态映射函数写法?
优化React-Redux connect的状态映射代码:更简洁的写法?
在React & Redux环境中,组件会接收特定的Redux状态,示例状态如下:
state = { users: ['bob', 'alice'], history: {param: 1}, releases: [1, 2], categories: [1, 2, 3], store: {dispatch: () => (), subscribe: () => ()}, contact: {message: ""}, hosts: ['company1', 'company2'], experiences: ['xp1', 'xp2'], }
当前我编写组件的方式如下:
import React, {Component} from "react"; import {connect} from "react-redux"; @connect(state => ({ hosts: state.hosts, experiences: state.experiences, categories: state.categories, })) export default class Home extends Component { ... }
我一直在尝试寻找缩减传递给connect的状态映射函数代码量的方法,尝试过以下几种写法:
@connect(({hosts, experiences, categories}) => ({hosts, experiences, categories})) @connect(state => _.pick(state, ['hosts', 'experiences', 'categories'])) @connect(({users, history, releases, store, contact, ...rest}) => rest)
但这些写法在可读性和DRY原则方面都不尽如人意。既然我们有扩展运算符和剩余运算符,为何无法消除这种冗余映射?像以下几种写法会简洁得多:
@connect(({hosts, experiences, categories}) => ...) @connect(...({hosts, experiences, categories})) @connect(() => ({hosts, experiences, categories})) @connect(({hosts, experiences, categories}))
请问是否存在我尚未了解的更优写法?
嘿,这个问题我太有共鸣了——谁不想少写点重复的状态映射代码呢?我来分享几个在项目里亲测好用、兼顾简洁性和可读性的方案:
1. 自定义pickState工具函数
这是我最常用的方案,自己封装一个轻量的工具函数,专门用来从Redux state中挑选需要的字段,复用性拉满:
// 可以放在项目的utils/redux.js文件中统一管理 export const pickState = (...keys) => (state) => { return keys.reduce((acc, key) => { acc[key] = state[key]; return acc; }, {}); };
之后在组件里使用就超级清爽,一眼就能看出你取了哪些状态字段:
import { pickState } from './utils/redux'; @connect(pickState('hosts', 'experiences', 'categories')) export default class Home extends Component { ... }
2. 结合Redux Toolkit的createSelector(推荐用于复杂场景)
如果你已经在使用Redux官方推荐的Redux Toolkit(RTK),可以用它的createSelector来创建复用的状态选择器。虽然对于简单的字段提取,它比上面的pickState稍显繁琐,但如果后续需要对状态做转换、或者需要利用它的缓存特性(避免不必要的重渲染),优势就很明显了:
import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; // 先定义单个字段的选择器 const selectHosts = (state) => state.hosts; const selectExperiences = (state) => state.experiences; const selectCategories = (state) => state.categories; // 组合成组件需要的props映射 const selectHomeProps = createSelector( [selectHosts, selectExperiences, selectCategories], (hosts, experiences, categories) => ({ hosts, experiences, categories }) ); @connect(selectHomeProps) export default class Home extends Component { ... }
3. 优化后的解构赋值写法(兼顾简洁与可读)
如果你不想额外封装工具函数,也可以把你之前的解构写法稍微调整得更紧凑一点,虽然还是有少量重复,但可读性比_.pick或者剩余运算符要好很多——毕竟不用去翻数组或者猜测rest里包含哪些字段:
@connect(({ hosts, experiences, categories }) => ({ hosts, experiences, categories }))
为什么你设想的那些写法行不通?
顺便解释下你提到的几种简洁写法为什么无法工作:
@connect(({hosts, experiences, categories}) => ...):箭头函数如果省略大括号,会把后面的内容当成返回值,但...在这里语法无效,如果你想返回解构后的对象,必须写成({ hosts, experiences, categories })(加括号让解析器识别为对象字面量)。@connect(...({hosts, experiences, categories})):展开对象的键会得到三个字符串,但connect需要接收的是一个状态映射函数,不是字符串参数,所以会报错。@connect(() => ({hosts, experiences, categories})):这个写法是直接返回一个固定对象,而不是从Redux state中获取对应字段,完全不符合需求。@connect(({hosts, experiences, categories})):这里你直接传递了解构后的对象,但connect第一个参数需要是函数,不是对象,所以运行时会抛出错误。
内容的提问来源于stack exchange,提问作者Adrien Lemaire
相关产品推荐
相关产品推荐

