Skip to content

Commit ce3a825

Browse files
committed
fix(styled): isolate attrs state to prevent cross-component contamination
- Replace shared defaultAttrs variable with parameter passing - Make .attrs() method return new instance instead of modifying shared state - Add test case to verify attrs isolation between components - Ensure each styled component maintains independent attrs
1 parent 27a208b commit ce3a825

File tree

2 files changed

+45
-14
lines changed

2 files changed

+45
-14
lines changed

packages/core/__tests__/styled.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,37 @@ describe('styled', () => {
108108
expect(style2?.color).eq('rgb(255, 0, 0)')
109109
})
110110

111+
it('should not mix attrs between different styled components', async () => {
112+
// 创建第一个组件,设置特定的 attrs
113+
const Component1 = styled.div.attrs({
114+
'data-testid': 'component1',
115+
'title': 'Component 1',
116+
})`
117+
color: red;
118+
`
119+
120+
// 创建第二个组件,设置不同的 attrs
121+
const Component2 = styled.div.attrs({
122+
'data-testid': 'component2',
123+
'title': 'Component 2',
124+
})`
125+
color: blue;
126+
`
127+
128+
// 渲染两个组件
129+
const instance1 = render(Component1)
130+
const instance2 = render(Component2)
131+
132+
const element1 = instance1.getByTestId('component1')
133+
const element2 = instance2.getByTestId('component2')
134+
135+
// 验证每个组件都有自己的 attrs,没有被混淆
136+
expect(element1.title).eq('Component 1')
137+
expect(element2.title).eq('Component 2')
138+
expect(element1.dataset.testid).eq('component1')
139+
expect(element2.dataset.testid).eq('component2')
140+
})
141+
111142
it('should react to props change', async () => {
112143
const StyledComponent = styled('div', { color: String }).attrs({ 'data-testid': 'test' })`
113144
color: ${props => props.color};

packages/core/src/styled.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ type TransformProps<T> = {
100100
: ConstructorToType<T[K]>
101101
}
102102

103-
function baseStyled<T extends object>(target: string | InstanceType<any>, propsDefinition?: PropsDefinition<T>): StyledComponent<T> {
103+
function baseStyled<T extends object>(target: string | InstanceType<any>, propsDefinition?: PropsDefinition<T>, defaultAttrs?: unknown): StyledComponent<T> {
104104
if (!isValidElementType(target)) {
105105
throw new Error('The element is invalid.')
106106
}
107-
let defaultAttrs: unknown
107+
108108
function styledComponent<P>(
109109
stylesOrProps: TemplateStringsArray | PropsDefinition<P> | CSSStyleObject | StyleFunction<P & TransformProps<T>>,
110110
...expressions: (
@@ -132,25 +132,25 @@ function baseStyled<T extends object>(target: string | InstanceType<any>, propsD
132132
}
133133
else {
134134
// 是props定义
135-
return baseStyled(target, { ...propsDefinition, ...stylesOrProps } as PropsDefinition<T & P>) as StyledComponent<T & P>
135+
return baseStyled(target, { ...propsDefinition, ...stylesOrProps } as PropsDefinition<T & P>, defaultAttrs) as StyledComponent<T & P>
136136
}
137137
}
138138

139139
// 正常的样式模板字符串处理
140140
const cssStringsWithExpression = insertExpressions(stylesOrProps as TemplateStringsArray, expressions)
141-
return createStyledComponent<P>(cssStringsWithExpression)
141+
return createStyledComponent<P>(cssStringsWithExpression, defaultAttrs)
142142
}
143143

144144
styledComponent.attrs = function <A = object>(
145145
attrs: object | ((props: PropsDefinition<T> & A) => object),
146146
) {
147-
defaultAttrs = attrs
148-
return styledComponent
147+
// 创建一个新的 styled 组件实例,而不是修改当前实例的共享状态
148+
return baseStyled(target, propsDefinition, attrs) as StyledComponent<A & T>
149149
}
150150

151151
// 添加props方法支持链式调用
152152
styledComponent.props = function <P extends object>(newPropsDefinition: PropsDefinition<P>) {
153-
return baseStyled(target, { ...propsDefinition, ...newPropsDefinition } as PropsDefinition<T & P>) as StyledComponent<T & P>
153+
return baseStyled(target, { ...propsDefinition, ...newPropsDefinition } as PropsDefinition<T & P>, defaultAttrs) as StyledComponent<T & P>
154154
}
155155

156156
// 将CSS对象转换为CSS字符串
@@ -168,7 +168,7 @@ function baseStyled<T extends object>(target: string | InstanceType<any>, propsD
168168
function createStyledComponentFromObject<P>(cssObject: CSSStyleObject) {
169169
const cssString = cssObjectToString(cssObject)
170170
const cssWithExpression = [cssString] as ExpressionType<any>[]
171-
return createStyledComponent<P>(cssWithExpression)
171+
return createStyledComponent<P>(cssWithExpression, defaultAttrs)
172172
}
173173

174174
// 从样式函数创建组件
@@ -178,10 +178,10 @@ function baseStyled<T extends object>(target: string | InstanceType<any>, propsD
178178
const cssObject = styleFunction(props)
179179
return cssObjectToString(cssObject)
180180
}] as ExpressionType<any>[]
181-
return createStyledComponent<P>(cssWithExpression)
181+
return createStyledComponent<P>(cssWithExpression, defaultAttrs)
182182
}
183183

184-
function createStyledComponent<P>(cssWithExpression: ExpressionType<any>[]) {
184+
function createStyledComponent<P>(cssWithExpression: ExpressionType<any>[], componentDefaultAttrs?: unknown) {
185185
let type: string = target
186186
if (isVueComponent(target)) {
187187
type = 'vue-component'
@@ -195,11 +195,11 @@ function baseStyled<T extends object>(target: string | InstanceType<any>, propsD
195195
const component = defineComponent(
196196
(props, { slots }) => {
197197
const internalAttrs = computed<Record<string, any>>(() => {
198-
if (typeof defaultAttrs === 'function') {
199-
return defaultAttrs(props)
198+
if (typeof componentDefaultAttrs === 'function') {
199+
return componentDefaultAttrs(props)
200200
}
201-
if (typeof defaultAttrs === 'object') {
202-
return defaultAttrs
201+
if (typeof componentDefaultAttrs === 'object') {
202+
return componentDefaultAttrs
203203
}
204204
return {}
205205
})

0 commit comments

Comments
 (0)