将样式化组件与复合组件模式一起使用时的依赖周期问题

时间:2019-06-19 17:13:05

标签: javascript reactjs styled-components

我有一些使用复合成分方法创建的手风琴成分(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道具添加自己的类,然后以这种方式引用它,但是我想知道是否有防止这种情况的方法? >

1 个答案:

答案 0 :(得分:3)

简而言之,styled-components生成一个className,必须 应用于HTML/JSX Element才能看到样式。另外,组成的组件必须是样式化组件的实例。我的示例here说明了样式自定义组件的基本方法。

由于我没有完整的代码,因此我从链接的视频中重新创建了示例。

工作示例(在这种情况下,我正在TabContent中设置Tab的样式并更改其svg元素):

Edit cool-sinoussi-spdpq

此外,我像这样对文件进行结构化,从而避免了导入递归问题:

├── 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>
  }
];