我有一些使用复合成分方法创建的手风琴成分(Ryan Florence在一篇精彩的演讲中描述了复合成分here)。
我设置的ESLint规则之一是import/no-cycle,以防止依赖圈。当我使用复合组件方法以及在样式化组件中建议使用refer to other components的建议方式时,通过将所有与特定组件相关的样式保持在该文件本身中,我触发了导入/无循环警告。 / p>
这是我的Accordion.js文件。
import React, { useState } from "react";
import AccordionTrigger from "./accordionTrigger";
import AccordionContent from "./accordionContent";
const Accordion = ({ children, show }) => {
const [isActive, setIsActive] = useState(show);
const handleTriggerClick = () => {
setIsActive(!isActive);
};
const compoundChildren = React.Children.map(children, child => {
switch (child.type) {
case AccordionTrigger:
return React.cloneElement(child, {
onClick: handleTriggerClick,
active: isActive ? 1 : 0,
});
case AccordionContent:
return React.cloneElement(child, {
show: isActive,
});
default:
return child;
}
});
return <div show={show ? 1 : 0}>{compoundChildren}</div>;
};
export default Accordion;
还有我的AccordionTrigger.js文件。
import React from "react";
import styled from "styled-components";
import FauxButton from "../buttons/fauxButton";
import Accordion from "./accordion";
import TopLevelTrigger from "./topLevelTrigger";
import SecondaryLevelTrigger from "./secondaryLevelTrigger";
const Root = styled(FauxButton)`
${Accordion} & {
width: 100%;
border-bottom: 1px solid ${p => p.theme.greyLight};
}
`;
const AccordionTrigger = ({ active, children, ...rest }) => {
const clonedChildren = React.Children.map(children, child => {
switch (child.type) {
case TopLevelTrigger:
case SecondaryLevelTrigger:
return React.cloneElement(child, {
active,
});
default:
return child;
}
});
return <Root {...rest}>{clonedChildren}</Root>;
};
export default AccordionTrigger;
我尝试过的一件事是像这样在AccordionTrigger
中定义Accordion.js
样式...
const Root = styled.div`
${AccordionTrigger} & {
width: 100%;
border-bottom: 1px solid ${p => p.theme.greyLight};
}
`;
const Accordion = ({ children, show }) => {
...same logic as before here
return <Root show={show ? 1 : 0}>{compoundChildren}</Root>;
};
...但是这不起作用,样式根本不会添加到AccordionTrigger
组件中。我知道我可以在克隆className
组件时通过Accordion
道具添加自己的类,然后以这种方式引用它,但是我想知道是否有防止这种情况的方法? >
答案 0 :(得分:3)
简而言之,styled-components
生成一个className
,必须 应用于HTML/JSX Element
才能看到样式。另外,组成的组件必须是样式化组件的实例。我的示例here说明了样式自定义组件的基本方法。
由于我没有完整的代码,因此我从链接的视频中重新创建了示例。
工作示例(在这种情况下,我正在TabContent
中设置Tab
的样式并更改其svg
元素):
此外,我像这样对文件进行结构化,从而避免了导入递归问题:
├── src
| ├── components
| | ├── Tab
| | | ├── Tab.js
| | | └── index.js
| | |
| | ├── TabContent
| | | ├── TabContent.js
| | | └── index.js
| | |
| | ├── TabList
| | | └── index.js
| | |
| | ├── TabPanel
| | | └── index.js
| | |
| | ├── TabPanels
| | | ├── TabPanels.js
| | | └── index.js
| | |
| | └── Tabs
| | └── index.js
| └── index.js
|
├── index.js
└── tabs.js
src / components / Tab / Tab.js
import React from "react";
import PropTypes from "prop-types";
const Tab = ({ children, className, disabled, onSelectTab }) => (
<div className={className} onClick={disabled ? null : onSelectTab}>
{children}
</div>
);
Tab.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
disabled: PropTypes.bool,
onSelectTab: PropTypes.func.isRequired
};
export default Tab;
src / components / Tab / index.js
import styled from "styled-components";
import Tab from "./Tab";
import TabContent from "../TabContent";
export default styled(Tab)`
display: inline-block;
padding: 10px;
margin: 10px;
border-bottom: 2px solid;
border-color: rgba(0, 0, 0, 0.65);
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
-webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
${({ disabled, isActive }) => {
if (disabled) return `opacity: 0.25; cursor: default;`;
if (isActive) return `color: #1890ff; border-bottom-color: #1890ff;`;
}}
&:hover {
color: #40a9ff;
border-bottom-color: #40a9ff;
${({ disabled }) =>
disabled &&
`color: rgba(0, 0, 0, 0.65); border-bottom-color: rgba(0, 0, 0, 0.65);`};
}
${TabContent} {
& svg {
font-size: 13px;
}
}
`;
src / components / TabContent / TabContent.js
import React from "react";
import PropTypes from "prop-types";
const TabContent = ({ children, className }) => (
<div className={className}>{children}</div>
);
TabContent.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.node.isRequired
};
export default TabContent;
src / components / TabContent / index.js
import styled from "styled-components";
import TabContent from "./TabContent";
export default styled(TabContent)`
font-size: 20px;
`;
src / components / TabList / index.js
import { Children, cloneElement, useCallback } from "react";
import PropTypes from "prop-types";
const TabList = ({ activeIndex, children, setActiveIndex }) => {
const handleSelectedTab = useCallback(
index => {
setActiveIndex(index);
},
[setActiveIndex]
);
return Children.map(children, (child, index) =>
cloneElement(child, {
isActive: activeIndex === index,
onSelectTab: () => handleSelectedTab(index)
})
);
};
TabList.propTypes = {
activeIndex: PropTypes.number,
children: PropTypes.node.isRequired,
setActiveIndex: PropTypes.func
};
export default TabList;
src / components / TabPanel / index.js
import PropTypes from "prop-types";
const TabPanel = ({ children }) => children;
TabPanel.propTypes = {
children: PropTypes.node.isRequired
};
export default TabPanel;
src / components / TabPanels / TabPanels.js
import React, { Children } from "react";
import PropTypes from "prop-types";
const TabPanels = ({ activeIndex, children, className }) => (
<div className={className}>{Children.toArray(children)[activeIndex]}</div>
);
TabPanels.propTypes = {
activeIndex: PropTypes.number,
children: PropTypes.node.isRequired,
setActiveIndex: PropTypes.func
};
export default TabPanels;
src / components / TabPanels / index.js
import styled from "styled-components";
import TabPanels from "./TabPanels";
export default styled(TabPanels)`
padding: 10px;
`;
src / components / Tabs / Tabs.js
import { Children, cloneElement, useState } from "react";
import PropTypes from "prop-types";
import TabPanels from "../TabPanels";
import TabList from "../TabList";
const Tabs = ({ children }) => {
const [activeIndex, setActiveIndex] = useState(0);
return Children.map(children, child => {
switch (child.type) {
case TabPanels: {
return cloneElement(child, { activeIndex });
}
case TabList: {
return cloneElement(child, {
activeIndex,
setActiveIndex
});
}
default: {
return child;
}
}
});
};
Tabs.propTypes = {
children: PropTypes.node.isRequired
};
export default Tabs;
src / components / index.js
export { default as Tab } from "./Tab";
export { default as TabContent } from "./TabContent";
export { default as TabList } from "./TabList";
export { default as TabPanel } from "./TabPanel";
export { default as TabPanels } from "./TabPanels";
export { default as Tabs } from "./Tabs";
src / index.js
import React from "react";
import ReactDOM from "react-dom";
import {
Tab,
TabContent,
Tabs,
TabList,
TabPanels,
TabPanel
} from "./components";
import tabs from "./tabs";
const App = () => (
<Tabs>
<TabList>
{tabs.map(({ icon, title, disabled }) => (
<Tab key={title} disabled={disabled}>
<TabContent>
{icon} {title}
</TabContent>
</Tab>
))}
</TabList>
<TabPanels>
{tabs.map(({ title, content }) => (
<TabPanel key={title}>{content}</TabPanel>
))}
</TabPanels>
</Tabs>
);
ReactDOM.render(<App />, document.getElementById("root"));
src / tabs.js
import React from "react";
import { FaAppleAlt, FaCarrot, FaLemon } from "react-icons/fa";
export default [
{
title: "Apples",
icon: <FaAppleAlt />,
content: <p>Apples are delicious.</p>
},
{
title: "Carrots",
icon: <FaCarrot />,
content: <p>Carrots are nutritious.</p>,
disabled: true
},
{
title: "Lemons",
icon: <FaLemon />,
content: <p>Lemons are ubiquitous.</p>
}
];