Skip to content

Commit

Permalink
Merge pull request #131 from Shopify/chore/perf-restyle-functions-com…
Browse files Browse the repository at this point in the history
…pose

Improve useRestyle performance
  • Loading branch information
sbalay authored Feb 24, 2022
2 parents c8cccfe + cdf1520 commit 963f2d3
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 49 deletions.
33 changes: 20 additions & 13 deletions src/composeRestyleFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BaseTheme,
Dimensions,
RNStyle,
RestyleFunction,
} from './types';
import {AllProps} from './restyleFunctions';

Expand All @@ -26,18 +27,19 @@ const composeRestyleFunctions = <
const properties = flattenedRestyleFunctions.map(styleFunc => {
return styleFunc.property;
});
const funcs = flattenedRestyleFunctions
.sort(
(styleFuncA, styleFuncB) =>
Number(styleFuncB.variant) - Number(styleFuncA.variant),
)
.map(styleFunc => {
return styleFunc.func;
});
const propertiesMap = properties.reduce(
(acc, prop) => ({...acc, [prop]: true}),
{} as Record<keyof TProps, true>,
);

const funcsMap = flattenedRestyleFunctions.reduce(
(acc, each) => ({[each.property]: each.func, ...acc}),
{} as Record<keyof TProps, RestyleFunction<TProps, Theme, string>>,
);

// TInputProps is a superset of TProps since TProps are only the Restyle Props
const buildStyle = <TInputProps extends TProps>(
props: TInputProps,
const buildStyle = (
props: TProps,
{
theme,
dimensions,
Expand All @@ -46,15 +48,20 @@ const composeRestyleFunctions = <
dimensions: Dimensions;
},
): RNStyle => {
const styles = funcs.reduce((acc, func) => {
return Object.assign(acc, func(props, {theme, dimensions}));
}, {});
const styles = Object.keys(props).reduce(
(styleObj, propKey) => ({
...styleObj,
...funcsMap[propKey as keyof TProps](props, {theme, dimensions}),
}),
{},
);
const {stylesheet} = StyleSheet.create({stylesheet: styles});
return stylesheet;
};
return {
buildStyle,
properties,
propertiesMap,
};
};

Expand Down
5 changes: 4 additions & 1 deletion src/createRestyleComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {View} from 'react-native';

import composeRestyleFunctions from './composeRestyleFunctions';
import {BaseTheme, RestyleFunctionContainer} from './types';
import useRestyle from './hooks/useRestyle';

Expand All @@ -13,8 +14,10 @@ const createRestyleComponent = <
| RestyleFunctionContainer<Props, Theme>[])[],
BaseComponent: React.ComponentType<any> = View,
) => {
const composedRestyleFunction = composeRestyleFunctions(restyleFunctions);

const RestyleComponent = React.forwardRef((props: Props, ref) => {
const passedProps = useRestyle(restyleFunctions, props);
const passedProps = useRestyle(composedRestyleFunction, props);
return <BaseComponent ref={ref} {...passedProps} />;
});
type RestyleComponentType = typeof RestyleComponent;
Expand Down
64 changes: 35 additions & 29 deletions src/hooks/useRestyle.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {useMemo} from 'react';
import {StyleProp} from 'react-native';
import {StyleProp, ViewStyle, TextStyle, ImageStyle} from 'react-native';

import {BaseTheme, RestyleFunctionContainer, RNStyle} from '../types';
import composeRestyleFunctions from '../composeRestyleFunctions';
import {BaseTheme, RNStyle, Dimensions} from '../types';
import {getKeys} from '../typeHelpers';

import useDimensions from './useDimensions';
Expand All @@ -13,24 +12,20 @@ const filterRestyleProps = <
TProps extends Record<string, unknown> & TRestyleProps
>(
props: TProps,
omitList: (keyof TRestyleProps)[],
): Omit<TProps, keyof TRestyleProps> => {
const omittedProp = omitList.reduce<Record<keyof TRestyleProps, boolean>>(
(acc, prop) => {
acc[prop] = true;
return acc;
},
{} as Record<keyof TRestyleProps, boolean>,
);

omitPropertiesMap: Record<keyof TProps, boolean>,
) => {
return getKeys(props).reduce(
(acc, key) => {
if (!omittedProp[key as keyof TRestyleProps]) {
acc[key] = props[key];
({cleanProps, restyleProps}, key) => {
if (omitPropertiesMap[key as keyof TProps]) {
return {cleanProps, restyleProps: {...restyleProps, [key]: props[key]}};
} else {
return {cleanProps: {...cleanProps, [key]: props[key]}, restyleProps};
}
return acc;
},
{} as TProps,
{cleanProps: {}, restyleProps: {}} as {
cleanProps: TProps;
restyleProps: TRestyleProps;
},
);
};

Expand All @@ -39,28 +34,39 @@ const useRestyle = <
TRestyleProps extends Record<string, any>,
TProps extends TRestyleProps & {style?: StyleProp<RNStyle>}
>(
restyleFunctions: (
| RestyleFunctionContainer<TProps, Theme>
| RestyleFunctionContainer<TProps, Theme>[])[],
composedRestyleFunction: {
buildStyle: <TInputProps extends TProps>(
props: TInputProps,
{
theme,
dimensions,
}: {
theme: Theme;
dimensions: Dimensions;
},
) => ViewStyle | TextStyle | ImageStyle;
properties: (keyof TProps)[];
propertiesMap: Record<keyof TProps, boolean>;
},
props: TProps,
) => {
const theme = useTheme<Theme>();

const dimensions = useDimensions();

const restyled = useMemo(() => {
const composedRestyleFunction = composeRestyleFunctions(restyleFunctions);
const style = composedRestyleFunction.buildStyle(props, {
const {cleanProps, restyleProps} = filterRestyleProps(
props,
composedRestyleFunction.propertiesMap,
);
const style = composedRestyleFunction.buildStyle(restyleProps, {
theme,
dimensions,
});
const cleanProps = filterRestyleProps(
props,
composedRestyleFunction.properties,
);
(cleanProps as TProps).style = [style, props.style].filter(Boolean);

cleanProps.style = [style, props.style].filter(Boolean);
return cleanProps;
}, [restyleFunctions, props, dimensions, theme]);
}, [composedRestyleFunction, props, dimensions, theme]);

return restyled;
};
Expand Down
19 changes: 13 additions & 6 deletions src/test/useRestyle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Text, TouchableOpacity} from 'react-native';
import useRestyle from '../hooks/useRestyle';
import {position, PositionProps} from '../restyleFunctions';
import createVariant, {VariantProps} from '../createVariant';
import composeRestyleFunctions from '../composeRestyleFunctions';

const theme = {
colors: {},
Expand All @@ -15,21 +16,27 @@ const theme = {
phone: 0,
tablet: 376,
},
zIndices: {
phone: 5,
},
};
type Theme = typeof theme;

type Props = VariantProps<Theme, 'buttonVariants'> &
PositionProps<Theme> & {
title: string;
} & ComponentPropsWithoutRef<typeof TouchableOpacity>;
PositionProps<Theme> &
ComponentPropsWithoutRef<typeof TouchableOpacity>;

const restyleFunctions = [
position,
createVariant({themeKey: 'buttonVariants'}),
createVariant<Theme>({themeKey: 'buttonVariants'}),
];

function Button({title, ...rest}: Props) {
const props = useRestyle(restyleFunctions, rest);
const composedRestyleFunction = composeRestyleFunctions<Theme, Props>(
restyleFunctions,
);

function Button({title, ...rest}: Props & {title: string}) {
const props = useRestyle(composedRestyleFunction, rest);
return (
<TouchableOpacity {...props}>
<Text>{title}</Text>
Expand Down

0 comments on commit 963f2d3

Please sign in to comment.