Skip to content

Commit 963f2d3

Browse files
authored
Merge pull request #131 from Shopify/chore/perf-restyle-functions-compose
Improve useRestyle performance
2 parents c8cccfe + cdf1520 commit 963f2d3

File tree

4 files changed

+72
-49
lines changed

4 files changed

+72
-49
lines changed

src/composeRestyleFunctions.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
BaseTheme,
66
Dimensions,
77
RNStyle,
8+
RestyleFunction,
89
} from './types';
910
import {AllProps} from './restyleFunctions';
1011

@@ -26,18 +27,19 @@ const composeRestyleFunctions = <
2627
const properties = flattenedRestyleFunctions.map(styleFunc => {
2728
return styleFunc.property;
2829
});
29-
const funcs = flattenedRestyleFunctions
30-
.sort(
31-
(styleFuncA, styleFuncB) =>
32-
Number(styleFuncB.variant) - Number(styleFuncA.variant),
33-
)
34-
.map(styleFunc => {
35-
return styleFunc.func;
36-
});
30+
const propertiesMap = properties.reduce(
31+
(acc, prop) => ({...acc, [prop]: true}),
32+
{} as Record<keyof TProps, true>,
33+
);
34+
35+
const funcsMap = flattenedRestyleFunctions.reduce(
36+
(acc, each) => ({[each.property]: each.func, ...acc}),
37+
{} as Record<keyof TProps, RestyleFunction<TProps, Theme, string>>,
38+
);
3739

3840
// TInputProps is a superset of TProps since TProps are only the Restyle Props
39-
const buildStyle = <TInputProps extends TProps>(
40-
props: TInputProps,
41+
const buildStyle = (
42+
props: TProps,
4143
{
4244
theme,
4345
dimensions,
@@ -46,15 +48,20 @@ const composeRestyleFunctions = <
4648
dimensions: Dimensions;
4749
},
4850
): RNStyle => {
49-
const styles = funcs.reduce((acc, func) => {
50-
return Object.assign(acc, func(props, {theme, dimensions}));
51-
}, {});
51+
const styles = Object.keys(props).reduce(
52+
(styleObj, propKey) => ({
53+
...styleObj,
54+
...funcsMap[propKey as keyof TProps](props, {theme, dimensions}),
55+
}),
56+
{},
57+
);
5258
const {stylesheet} = StyleSheet.create({stylesheet: styles});
5359
return stylesheet;
5460
};
5561
return {
5662
buildStyle,
5763
properties,
64+
propertiesMap,
5865
};
5966
};
6067

src/createRestyleComponent.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import {View} from 'react-native';
33

4+
import composeRestyleFunctions from './composeRestyleFunctions';
45
import {BaseTheme, RestyleFunctionContainer} from './types';
56
import useRestyle from './hooks/useRestyle';
67

@@ -13,8 +14,10 @@ const createRestyleComponent = <
1314
| RestyleFunctionContainer<Props, Theme>[])[],
1415
BaseComponent: React.ComponentType<any> = View,
1516
) => {
17+
const composedRestyleFunction = composeRestyleFunctions(restyleFunctions);
18+
1619
const RestyleComponent = React.forwardRef((props: Props, ref) => {
17-
const passedProps = useRestyle(restyleFunctions, props);
20+
const passedProps = useRestyle(composedRestyleFunction, props);
1821
return <BaseComponent ref={ref} {...passedProps} />;
1922
});
2023
type RestyleComponentType = typeof RestyleComponent;

src/hooks/useRestyle.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {useMemo} from 'react';
2-
import {StyleProp} from 'react-native';
2+
import {StyleProp, ViewStyle, TextStyle, ImageStyle} from 'react-native';
33

4-
import {BaseTheme, RestyleFunctionContainer, RNStyle} from '../types';
5-
import composeRestyleFunctions from '../composeRestyleFunctions';
4+
import {BaseTheme, RNStyle, Dimensions} from '../types';
65
import {getKeys} from '../typeHelpers';
76

87
import useDimensions from './useDimensions';
@@ -13,24 +12,20 @@ const filterRestyleProps = <
1312
TProps extends Record<string, unknown> & TRestyleProps
1413
>(
1514
props: TProps,
16-
omitList: (keyof TRestyleProps)[],
17-
): Omit<TProps, keyof TRestyleProps> => {
18-
const omittedProp = omitList.reduce<Record<keyof TRestyleProps, boolean>>(
19-
(acc, prop) => {
20-
acc[prop] = true;
21-
return acc;
22-
},
23-
{} as Record<keyof TRestyleProps, boolean>,
24-
);
25-
15+
omitPropertiesMap: Record<keyof TProps, boolean>,
16+
) => {
2617
return getKeys(props).reduce(
27-
(acc, key) => {
28-
if (!omittedProp[key as keyof TRestyleProps]) {
29-
acc[key] = props[key];
18+
({cleanProps, restyleProps}, key) => {
19+
if (omitPropertiesMap[key as keyof TProps]) {
20+
return {cleanProps, restyleProps: {...restyleProps, [key]: props[key]}};
21+
} else {
22+
return {cleanProps: {...cleanProps, [key]: props[key]}, restyleProps};
3023
}
31-
return acc;
3224
},
33-
{} as TProps,
25+
{cleanProps: {}, restyleProps: {}} as {
26+
cleanProps: TProps;
27+
restyleProps: TRestyleProps;
28+
},
3429
);
3530
};
3631

@@ -39,28 +34,39 @@ const useRestyle = <
3934
TRestyleProps extends Record<string, any>,
4035
TProps extends TRestyleProps & {style?: StyleProp<RNStyle>}
4136
>(
42-
restyleFunctions: (
43-
| RestyleFunctionContainer<TProps, Theme>
44-
| RestyleFunctionContainer<TProps, Theme>[])[],
37+
composedRestyleFunction: {
38+
buildStyle: <TInputProps extends TProps>(
39+
props: TInputProps,
40+
{
41+
theme,
42+
dimensions,
43+
}: {
44+
theme: Theme;
45+
dimensions: Dimensions;
46+
},
47+
) => ViewStyle | TextStyle | ImageStyle;
48+
properties: (keyof TProps)[];
49+
propertiesMap: Record<keyof TProps, boolean>;
50+
},
4551
props: TProps,
4652
) => {
4753
const theme = useTheme<Theme>();
4854

4955
const dimensions = useDimensions();
5056

5157
const restyled = useMemo(() => {
52-
const composedRestyleFunction = composeRestyleFunctions(restyleFunctions);
53-
const style = composedRestyleFunction.buildStyle(props, {
58+
const {cleanProps, restyleProps} = filterRestyleProps(
59+
props,
60+
composedRestyleFunction.propertiesMap,
61+
);
62+
const style = composedRestyleFunction.buildStyle(restyleProps, {
5463
theme,
5564
dimensions,
5665
});
57-
const cleanProps = filterRestyleProps(
58-
props,
59-
composedRestyleFunction.properties,
60-
);
61-
(cleanProps as TProps).style = [style, props.style].filter(Boolean);
66+
67+
cleanProps.style = [style, props.style].filter(Boolean);
6268
return cleanProps;
63-
}, [restyleFunctions, props, dimensions, theme]);
69+
}, [composedRestyleFunction, props, dimensions, theme]);
6470

6571
return restyled;
6672
};

src/test/useRestyle.test.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Text, TouchableOpacity} from 'react-native';
44
import useRestyle from '../hooks/useRestyle';
55
import {position, PositionProps} from '../restyleFunctions';
66
import createVariant, {VariantProps} from '../createVariant';
7+
import composeRestyleFunctions from '../composeRestyleFunctions';
78

89
const theme = {
910
colors: {},
@@ -15,21 +16,27 @@ const theme = {
1516
phone: 0,
1617
tablet: 376,
1718
},
19+
zIndices: {
20+
phone: 5,
21+
},
1822
};
1923
type Theme = typeof theme;
2024

2125
type Props = VariantProps<Theme, 'buttonVariants'> &
22-
PositionProps<Theme> & {
23-
title: string;
24-
} & ComponentPropsWithoutRef<typeof TouchableOpacity>;
26+
PositionProps<Theme> &
27+
ComponentPropsWithoutRef<typeof TouchableOpacity>;
2528

2629
const restyleFunctions = [
2730
position,
28-
createVariant({themeKey: 'buttonVariants'}),
31+
createVariant<Theme>({themeKey: 'buttonVariants'}),
2932
];
3033

31-
function Button({title, ...rest}: Props) {
32-
const props = useRestyle(restyleFunctions, rest);
34+
const composedRestyleFunction = composeRestyleFunctions<Theme, Props>(
35+
restyleFunctions,
36+
);
37+
38+
function Button({title, ...rest}: Props & {title: string}) {
39+
const props = useRestyle(composedRestyleFunction, rest);
3340
return (
3441
<TouchableOpacity {...props}>
3542
<Text>{title}</Text>

0 commit comments

Comments
 (0)