From 84f828c39e194d1a2d92c88f713db26157ec141d Mon Sep 17 00:00:00 2001 From: arvinxx Date: Sun, 26 Feb 2023 17:38:21 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat:=20=E7=AE=80=E5=8C=96=20Theme?= =?UTF-8?q?Provider=20=E6=B3=A8=E5=85=A5=20styled=20=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGES: 移除 styled.useTheme 的用法,需要调整为 styled.ThemeContext,且 styled 的ThemeProvider 可以无需注入 --- src/factories/createStyledThemeProvider.tsx | 15 ++++ src/factories/createThemeProvider/index.tsx | 78 ++++++++++++--------- src/factories/createUseTheme.ts | 10 +-- src/functions/createInstance.ts | 6 +- src/functions/setupStyled.ts | 16 +++-- src/types/styled.ts | 13 +++- tests/functions/styled.test.tsx | 8 ++- 7 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 src/factories/createStyledThemeProvider.tsx diff --git a/src/factories/createStyledThemeProvider.tsx b/src/factories/createStyledThemeProvider.tsx new file mode 100644 index 00000000..18741d2a --- /dev/null +++ b/src/factories/createStyledThemeProvider.tsx @@ -0,0 +1,15 @@ +import { StyledConfig, StyledThemeProvider } from '@/types'; + +/** + * 创建一个 styled api 的 ThemeProvider + * 如果用户有设定 ThemeProvider,就使用用户的,否则使用 ThemeContext.Provider + * @param styledConfig + */ +export const createStyledThemeProvider = (styledConfig: StyledConfig): StyledThemeProvider => { + if (styledConfig.ThemeProvider) return styledConfig.ThemeProvider; + + const { ThemeContext } = styledConfig; + return (props) => ( + {props.children} + ); +}; diff --git a/src/factories/createThemeProvider/index.tsx b/src/factories/createThemeProvider/index.tsx index 364aa553..afd91af7 100644 --- a/src/factories/createThemeProvider/index.tsx +++ b/src/factories/createThemeProvider/index.tsx @@ -1,9 +1,10 @@ import { Context, memo, ReactElement } from 'react'; import { createUseTheme } from '@/factories/createUseTheme'; -import { DEFAULT_THEME_PROVIDER, DEFAULT_USE_THEME } from '@/functions/setupStyled'; +import { DEFAULT_THEME_CONTEXT, DEFAULT_THEME_PROVIDER } from '@/functions/setupStyled'; import { StyledConfig } from '@/types'; +import { createStyledThemeProvider } from '@/factories/createStyledThemeProvider'; import AntdProvider from './AntdProvider'; import ThemeSwitcher from './ThemeSwitcher'; import TokenContainer from './TokenContainer'; @@ -20,8 +21,13 @@ interface CreateThemeProviderOptions { export const createThemeProvider = ( option: CreateThemeProviderOptions, -): ((props: ThemeProviderProps) => ReactElement | null) => - memo( +): ((props: ThemeProviderProps) => ReactElement | null) => { + // 如果有全局配置 styledConfig,那么 ThemeProvider + const DefaultStyledThemeProvider = option.styledConfig + ? createStyledThemeProvider(option.styledConfig) + : undefined; + + return memo( ({ children, @@ -38,36 +44,44 @@ export const createThemeProvider = ( onAppearanceChange, themeMode, styled, - }) => ( - - { + const useTheme = createUseTheme({ + prefixCls: prefixCls || option.prefixCls, + styledThemeContext: + styled?.ThemeContext || option.styledConfig?.ThemeContext || DEFAULT_THEME_CONTEXT, + CustomThemeContext: option.CustomThemeContext, + }); + + const StyledThemeProvider = styled + ? createStyledThemeProvider(styled) + : DefaultStyledThemeProvider || DEFAULT_THEME_PROVIDER; + + return ( + - - {children} - - - - ), + + {children} + + + + ); + }, ); +}; diff --git a/src/factories/createUseTheme.ts b/src/factories/createUseTheme.ts index a9caffb9..d6ff3ebd 100644 --- a/src/factories/createUseTheme.ts +++ b/src/factories/createUseTheme.ts @@ -1,24 +1,24 @@ -import { Theme, UseTheme } from '@/types'; +import { Theme } from '@/types'; import { Context, useContext, useMemo } from 'react'; -import { DEFAULT_USE_THEME } from '@/functions/setupStyled'; +import { DEFAULT_THEME_CONTEXT } from '@/functions/setupStyled'; import { useAntdTheme } from '@/hooks/useAntdTheme'; import { useThemeMode } from '@/hooks/useThemeMode'; interface CreateUseThemeOptions { prefixCls?: string; CustomThemeContext: Context; - styledUseTheme?: UseTheme; + styledThemeContext?: Context; } export const createUseTheme = (options: CreateUseThemeOptions) => (): Theme => { - const { prefixCls, styledUseTheme, CustomThemeContext } = options; + const { prefixCls, styledThemeContext, CustomThemeContext } = options; const antdTheme = useAntdTheme(); const themeState = useThemeMode(); const defaultCustomTheme = useContext(CustomThemeContext); - const styledTheme = styledUseTheme ? styledUseTheme() : DEFAULT_USE_THEME() || {}; + const styledTheme = useContext(styledThemeContext ?? DEFAULT_THEME_CONTEXT) || {}; const initTheme = useMemo( () => ({ diff --git a/src/functions/createInstance.ts b/src/functions/createInstance.ts index 2c09e381..466a929c 100644 --- a/src/functions/createInstance.ts +++ b/src/functions/createInstance.ts @@ -10,7 +10,7 @@ import { createStylesFactory } from '@/factories/createStyles'; import { createThemeProvider, ThemeProviderProps } from '@/factories/createThemeProvider'; import { createUseTheme } from '@/factories/createUseTheme'; -import { HashPriority, StyledConfig, StyleManager, Theme } from '@/types'; +import { HashPriority, StyledConfig, StyleManager } from '@/types'; // 为 SSR 添加一个全局的 cacheManager,用于统一抽取 ssr 样式 declare global { @@ -70,12 +70,12 @@ export const createInstance = (options: CreateOptions) => { (options.customToken ? options.customToken : {}) as T, ); - const styledUseTheme = options.styled?.useTheme as () => Theme; + const styledThemeContext = options.styled?.ThemeContext; const useTheme = createUseTheme({ prefixCls: options?.prefixCls, CustomThemeContext, - styledUseTheme, + styledThemeContext, }); const createStyles = createStylesFactory({ diff --git a/src/functions/setupStyled.ts b/src/functions/setupStyled.ts index d932baea..3ea73dbc 100644 --- a/src/functions/setupStyled.ts +++ b/src/functions/setupStyled.ts @@ -1,15 +1,17 @@ -import { ThemeProvider, useTheme } from '@emotion/react'; +import { ThemeContext, ThemeProvider } from '@emotion/react'; +import { Context } from 'react'; +import { createStyledThemeProvider } from '@/factories/createStyledThemeProvider'; import { StyledConfig, StyledThemeProvider, Theme } from '@/types'; export let DEFAULT_THEME_PROVIDER = ThemeProvider as StyledThemeProvider; -export let DEFAULT_USE_THEME = useTheme as () => Theme; +export let DEFAULT_THEME_CONTEXT = ThemeContext as Context; export const setupStyled = (config: StyledConfig) => { - if (config?.ThemeProvider) { - DEFAULT_THEME_PROVIDER = config.ThemeProvider; - } - if (config.useTheme) { - DEFAULT_USE_THEME = config.useTheme; + if (!config.ThemeContext) { + throw 'ThemeContext is required. Please check your config.'; } + + DEFAULT_THEME_CONTEXT = config.ThemeContext; + DEFAULT_THEME_PROVIDER = createStyledThemeProvider(config); }; diff --git a/src/types/styled.ts b/src/types/styled.ts index 2955d182..09df11ab 100644 --- a/src/types/styled.ts +++ b/src/types/styled.ts @@ -1,9 +1,16 @@ -import { Theme } from 'antd-style/src'; -import { FC, ReactNode } from 'react'; +import { Context, FC, ReactNode } from 'react'; +import { Theme } from './theme'; export interface StyledConfig { + /** + * styled 对象所对应的 ThemeContext + * @requires + */ + ThemeContext: Context; + /** + * 可以注入相应 styled 方法的 ThemeProvider,或其他自己定义的ThemeProvider + */ ThemeProvider?: StyledThemeProvider; - useTheme?: () => any; } export type StyledThemeProvider = FC<{ theme: Theme; children: ReactNode }>; diff --git a/tests/functions/styled.test.tsx b/tests/functions/styled.test.tsx index ad288100..afdcfa11 100644 --- a/tests/functions/styled.test.tsx +++ b/tests/functions/styled.test.tsx @@ -1,6 +1,12 @@ import styled from '@emotion/styled'; import { render } from '@testing-library/react'; -import { ThemeProvider } from 'antd-style'; +import { Theme as AntdStyleTheme, ThemeProvider } from 'antd-style'; + +// 为 emotion 的 styled 注入 antd-style 的主题类型 +declare module '@emotion/react' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface Theme extends AntdStyleTheme {} +} describe('styled', () => { it('类型定义正常', () => {