2

我正在尝试创建一个自定义钩子,它将为我们处理一些 Aphrodite ( https://github.com/Khan/aphrodite ) 样式的合并,但是在传入样式对象时我遇到了一些不寻常的行为。

这是我的钩子:(计划是用 useMemo 组成,这就是为什么它是一个钩子)

import castArray from 'lodash/castArray';
import {StyleSheet} from 'aphrodite';
import merge from 'lodash/merge';

export const useStyles = (styles, props) => {
    return {
        styles: StyleSheet.create(
            [...castArray(styles), ...castArray(props.styles)]
                .reduce((agg, next) => (merge(agg, next))),
        )
    };
};

以及基本用法:

function TestComponent(props) {
    const {styles} = useStyles(baseStyles, props);

    return <div className={css(styles.test)}>Test Text</div>
}

想法是,如果styles传入一个 prop,它将与 baseStyles 中的任何内容合并,并给出最终的 Aphrodite Stylesheet 用于该组件实例。

我在这里创建了一个简单的 repro 存储库:https ://github.com/bslinger/hooks-question

预期:在两条路线之间单击会改变文本的颜色,具体取决于是否已传入道具以覆盖该样式。

实际:一旦样式被合并,即使没有传入额外道具的路线也会以覆盖的颜色显示它。

注意:我意识到在删除 useMemo 之后,这在技术上甚至不是一个钩子,并且将 React 降级到 16.7 会导致相同的行为,所以我猜这毕竟只是一个 Javascript 或 React 问题?

4

1 回答 1

2

这里的关键是详细了解Array.reduce的行为。reduce接受两个参数。第一个参数是您指定的回调。第二个参数是可选的,是累加器的初始值(回调的第一个参数)。以下是该论点的描述:

用作回调第一次调用的第一个参数的值。如果未提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用 reduce() 是错误的。

为了更容易理解它的效果,这将有助于简化你的语法。

只要stylesandprops.styles不是数组(它们不在您的示例中),以下内容:

[...castArray(styles), ...castArray(props.styles)]

相当于:

[styles, props.styles]

因此,在函数没有 initialValue 的情况下reduce,累加器将成为数组中的第一个元素:styles. 所以一旦你执行了“withProps”场景,你已经改变了对象,styles.js没有任何东西可以将它改回原来的绿色。如果styles是一个数组(使用原始代码),那么该副作用将发生在该数组中的第一个样式对象上。

要解决此问题,您只需为累加器指定一个初始值:

export const useStyles = (styles, props) => {
  return {
    styles: StyleSheet.create(
      [...castArray(styles), ...castArray(props.styles)].reduce(
        (agg, next) => merge(agg, next),
        {} // Here's an empty object as the accumulator initial value
      )
    )
  };
};

编辑 yqjjmmp21x

于 2019-02-12T05:02:06.637 回答