From 09035d896c87a82f9c239ac5febd28cc333dbb8b Mon Sep 17 00:00:00 2001 From: jaywcjlove Date: Sat, 8 Oct 2022 15:56:45 +0000 Subject: [PATCH] feat: add `styled-components.md` cheatsheet. 2fcd80f34db5cb05e64b753a4dc91d64c48c2e2f --- docs/docker.html | 3 +- docs/jest.html | 186 +++++-- docs/package.json.html | 4 +- docs/styled-components.html | 962 ++++++++++++++++++++++++++++++++++++ index.html | 4 + style/style.css | 4 +- 6 files changed, 1113 insertions(+), 50 deletions(-) create mode 100644 docs/styled-components.html diff --git a/docs/docker.html b/docs/docker.html index cd6544e3..3d089429 100644 --- a/docs/docker.html +++ b/docs/docker.html @@ -49,11 +49,12 @@
  • docker/getting-started - 要使用的镜像
  • 在前台创建并运行容器

    -
    $ docker run -it -p 8001:8080 --name my-nginx nginx
    +
    $ docker run -it -p --rm 8001:8080 --name my-nginx nginx
     

    +

    快照

    + +
    expect(value)
    +  .toMatchSnapshot()
    +  .toMatchInlineSnapshot()
    +
    +

    Errors

    + +
    expect(value)
    +  .toThrow(error)
    +  .toThrowErrorMatchingSnapshot()
    +
    +

    Objects

    + +
    expect(value)
    +  .toBeInstanceOf(Class)
    +  .toMatchObject(object)
    +  .toHaveProperty(keyPath, value)
    +
    +

    Objects

    + +
    expect(value)
    +  .toContain(item)
    +  .toContainEqual(item)
    +  .toHaveLength(number)
    +
    +

    Numbers

    + +
    expect(value)
    +  .toBeCloseTo(number, numDigits)
    +  .toBeGreaterThan(number)
    +  .toBeGreaterThanOrEqual(number)
    +  .toBeLessThan(number)
    +  .toBeLessThanOrEqual(number)
    +
    +

    Booleans

    + +
    expect(value)
    +  .toBeFalsy()
    +  .toBeNull()
    +  .toBeTruthy()
    +  .toBeUndefined()
    +  .toBeDefined()
    +
    +

    Strings

    + +
    expect(value)
    +  .toMatch(regexpOrString)
    +
    +

    NaN

    + +
    test('当值为 NaN 时通过', () => {
    +  expect(NaN).toBeNaN();
    +  expect(1).not.toBeNaN();
    +});
    +
    +

    Others

    + +
    expect.extend(matchers)
    +expect.any(constructor)
    +expect.addSnapshotSerializer(serializer)
    +
    +expect.assertions(1)
    +

    匹配器

    基本匹配器

    expect(42).toBe(42)    // 严格相等 (===)
    @@ -291,7 +355,7 @@
     // 匹配除 null 或 undefined 之外的任何内容
     expect('pizza').toEqual(expect.anything())
     
    -

    快照

    +

    快照

    // 这可确保某个值与最近的快照匹配。
     expect(node).toMatchSnapshot()
     
    @@ -360,7 +424,6 @@
     

    异步测试

    实例

    -

    请参阅 Jest 文档中的 更多示例

    在异步测试中指定一些预期的断言是一个很好的做法,所以如果你的断言根本没有被调用,测试将会失败。

    test('async test', () => {
       // 在测试期间恰好调用了三个断言
    @@ -373,7 +436,8 @@
     

    请注意,您也可以在任何 describetest 之外对每个文件执行此操作:

    beforeEach(expect.hasAssertions)
     
    -

    这将验证每个测试用例至少存在一个断言。 它还可以与更具体的 expect.assertions(3) 声明配合使用。

    +

    这将验证每个测试用例至少存在一个断言。 它还可以与更具体的 expect.assertions(3) 声明配合使用。 +请参阅 Jest 文档中的 更多示例

    async/await

    test('async test', async () => {
       expect.assertions(1)
    @@ -391,8 +455,8 @@
       
       setTimeout(() => {
         try {
    -      const result = getAsyncOperationResult()
    -      expect(result).toBe(true)
    +      const res = getAsyncOperatResult()
    +      expect(res).toBe(true)
           done()
         } catch (err) {
           done.fail(err)
    @@ -414,19 +478,29 @@
     

    模拟

    模拟函数

    -
    test('call the callback', () => {
    +
    test('call the callback', () => {
       const callback = jest.fn()
       fn(callback)
       expect(callback).toBeCalled()
    -  expect(callback.mock.calls[0][1].baz).toBe('pizza') // 第一次调用的第二个参数
    +  expect(callback.mock.calls[0][1].baz)
    +    .toBe('pizza') // 第一次调用的第二个参数
    +  
       // 匹配第一个和最后一个参数,但忽略第二个参数
    -  expect(callback).toHaveBeenLastCalledWith('meal', expect.anything(), 'margarita')
    +  expect(callback)
    +    .toHaveBeenLastCalledWith(
    +      'meal',
    +      expect.anything(),
    +      'margarita'
    +    )
     })
     
    +

    您还可以使用快照:

    test('call the callback', () => {
       // mockName 在 Jest 22+ 中可用
    -  const callback = jest.fn().mockName('Unicorn') 
    +  const callback = jest.fn()
    +    .mockName('Unicorn')
    +
       fn(callback)
       expect(callback).toMatchSnapshot()
       // ->
    @@ -444,36 +518,46 @@
     

    您的模拟可以返回值:

    const callback
         = jest.fn().mockReturnValue(true)
    +
     const callbackOnce
         = jest.fn().mockReturnValueOnce(true)
     

    或解析值:

    -
    const promise 
    +
    const promise
         = jest.fn().mockResolvedValue(true)
    -const promiseOnce 
    +
    +const promiseOnce
         = jest.fn().mockResolvedValueOnce(true)
     

    他们甚至可以拒绝值:

    -
    const failedPromise
    -    = jest.fn().mockRejectedValue('Error')
    -const failedPromiseOnce
    -    = jest.fn().mockRejectedValueOnce('Error')
    +
    const failedPromise =
    +  jest.fn().mockRejectedValue('Error')
    +
    +const failedPromiseOnce =
    +  jest.fn().mockRejectedValueOnce('Error')
     

    你甚至可以结合这些:

    -
    const callback
    -    = jest.fn().mockReturnValueOnce(false).mockReturnValue(true)
    +
    const callback = jest.fn()
    +        .mockReturnValueOnce(false)
    +        .mockReturnValue(true)
     // ->
     //  call 1: false
     //  call 2+: true
     

    使用 jest.mock 方法模拟模块

    -
    jest.mock('lodash/memoize', () => (a) => a) // The original lodash/memoize should exist
    -jest.mock('lodash/memoize', () => (a) => a, { virtual: true }) // The original lodash/memoize isn’t required
    +
    // 原来的 lodash/memoize 应该存在
    +jest.mock(
    +  'lodash/memoize',
    +  () => (a) => a
    +)
    +// 不需要原始的 lodash/memoize
    +jest.mock(
    +  'lodash/memoize',
    +  () => (a) => a,
    +  { virtual: true }
    +)
     
    -

    jest.mock docs

    -
    -

    注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock

    -
    +

    注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock

    使用模拟文件模拟模块

    创建一个类似 __mocks__/lodash/memoize.js 的文件:

    module.exports = (a) => a
    @@ -481,16 +565,15 @@
     

    添加到您的测试中:

    jest.mock('lodash/memoize')
     
    -

    注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock

    -

    手动模拟文档

    -

    模拟对象方法

    -
    const spy = jest.spyOn(console, 'log').mockImplementation(() => {})
    -expect(console.log.mock.calls).toEqual([['dope'], ['nope']])
    -spy.mockRestore()
    -
    -
    const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true }))
    -expect(spy).toHaveBeenCalled()
    -spy.mockRestore()
    +

    注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock手动模拟文档

    +

    模拟 getters 和 setters

    +
    const getTitle = jest.fn(() => 'pizza')
    +const setTitle = jest.fn()
    +const location = {}
    +Object.defineProperty(location, 'title', {
    +  get: getTitle,
    +  set: setTitle,
    +})
     

    模拟 getter 和 setter (Jest 22.1.0+)

    const location = {}
    @@ -508,8 +591,9 @@
     jest.useFakeTimers()
     test('kill the time', () => {
       const callback = jest.fn()
    -  // 运行一些使用 setTimeout 或 setInterval 的代码
    -  const actual = someFunctionThatUseTimers(callback)
    +  // 运行使用 setTimeout或setInterval 的代码
    +  const actual 
    +    = someFunctionThatUseTimers(callback)
       // 快进直到所有定时器都执行完毕
       jest.runAllTimers()
       // 同步检查结果
    @@ -521,24 +605,34 @@
     jest.useFakeTimers()
     test('kill the time', () => {
       const callback = jest.fn()
    -  // 运行一些使用 setTimeout 或 setInterval 的代码
    -  const actual = someFunctionThatUseTimers(callback)
    +  // 运行使用 setTimeout或setInterval 的代码
    +  const actual 
    +    = someFunctionThatUseTimers(callback)
       // 快进 250 毫秒
       jest.advanceTimersByTime(250)
       // 同步检查结果
       expect(callback).toHaveBeenCalledTimes(1)
     })
     
    +

    对于特殊情况,请使用 jest.runOnlyPendingTimers()

    +

    注意: 您应该在测试用例中调用 jest.useFakeTimers() 以使用其他假计时器方法。

    -

    模拟 getters 和 setters

    -
    const getTitle = jest.fn(() => 'pizza')
    -const setTitle = jest.fn()
    -const location = {}
    -Object.defineProperty(location, 'title', {
    -  get: getTitle,
    -  set: setTitle,
    -})
    +

    模拟对象方法

    +
    const spy = jest.spyOn(console, 'log')
    +  .mockImplementation(() => {})
    +
    +expect(console.log.mock.calls)
    +  .toEqual([['dope'], ['nope']])
    +spy.mockRestore()
    +
    +
    const spy = jest.spyOn(ajax, 'request')
    +  .mockImplementation(
    +    () => Promise.resolve({success: true})
    +  )
    +
    +expect(spy).toHaveBeenCalled()
    +spy.mockRestore()
     

    清除和恢复模拟

    @@ -546,8 +640,10 @@
    // 清除模拟使用日期
     // (fn.mock.calls、fn.mock.instances)
     fn.mockClear()
    +
     // 清除并删除任何模拟的返回值或实现
     fn.mockReset()
    +
     // 重置并恢复初始实现
     fn.mockRestore()
     
    diff --git a/docs/package.json.html b/docs/package.json.html index bfb2f968..9f384203 100644 --- a/docs/package.json.html +++ b/docs/package.json.html @@ -102,14 +102,14 @@ }

    鼓励使用开源 (OSI-approved) 许可证,除非你有特别的原因不用它。 如果你开发的包是你工作的一部分,最好和公司讨论后再做决定。

    -

    license字段必须是以下之一:

    +

    license字段必须是以下之一

    • 如果你使用标准的许可证,需要一个有效地 SPDX 许可证标识
    • 如果你用多种标准许可证,需要有效的 SPDX 许可证表达式2.0语法表达式
    • 如果你使用非标准的许可证,一个 SEE LICENSE IN <文件名> 字符串指向你的包里顶级目录的一个 <文件名>。
    • 如果你不想在任何条款下授权其他人使用你的私有或未公开的包,一个 UNLICENSED 字符串。
    -

    keywords

    +

    keywords

    {
       "keywords": [
         "short", "relevant", "keywords"
    diff --git a/docs/styled-components.html b/docs/styled-components.html
    new file mode 100644
    index 00000000..733593b1
    --- /dev/null
    +++ b/docs/styled-components.html
    @@ -0,0 +1,962 @@
    +
    +
    +
    +
    +styled-components 备忘清单
    + &  styled-components cheatsheet &  Quick Reference
    +
    +
    +
    +
    +
    +
    +

    styled-components 备忘清单

    +

    此快速参考备忘单提供了使用 CSS in JS 工具的各种方法。

    +

    入门

    +

    安装

    +

    Styled Components 是增强 CSS 在 React 组件系统样式的 CSS in JS 的最佳实践。

    + +

    安装依赖和 TypeScript 类型依赖

    +
    npm install --save styled-components
    +
    +

    快速开始

    + +
    import styled from 'styled-components';
    +
    +

    创建一个 Title 组件

    +
    // 该组件将呈现具有样式的 <h1> 标签
    +const Title = styled.h1`
    +  font-size: 1.5em;
    +  text-align: center;
    +`;
    +
    +

    创建一个 Wrapper 组件

    +
    // 该组件将呈现具有某些样式的 <section> 标记
    +const Wrapper = styled.section`
    +  padding: 4em;
    +  background: papayawhip;
    +`;
    +
    +

    像使用其他 React 组件一样使用 Title/Wrapper - 除了它们的样式!

    +
    function Demo() {
    +  return (
    +    <Wrapper>
    +      <Title>
    +        Hello World!
    +      </Title>
    +    </Wrapper>
    +  );
    +}
    +
    +

    根据 Props 适配

    + +
    import styled from 'styled-components';
    +
    +const Button = styled.button`
    +  /* 根据主要 props 调整颜色 */
    +  background: ${
    +    props => 
    +      props.primary ? "blue" : "white"
    +  };
    +  color: ${
    +    props => 
    +      props.primary ? "white" : "blue"
    +  };
    +  font-size: 1em;
    +  margin: 1em;
    +  padding: 0.25em 1em;
    +  border: 2px solid blue;
    +  border-radius: 3px;
    +`;
    +
    +

    使用 primary props 控制按钮样式

    +
    function Demo() {
    +  return (
    +    <div>
    +      <Button>Normal</Button>
    +      <Button primary>Primary</Button>
    +    </div>
    +  );
    +}
    +
    +

    扩展样式

    +
    const Button = styled.button`
    +  color: palevioletred;
    +  border: 2px solid palevioletred;
    +  border-radius: 3px;
    +`;
    +// 基于 Button 的新组件,但具有一些覆盖样式
    +const TomatoButton = styled(Button)`
    +  color: tomato;
    +  border-color: tomato;
    +`;
    +const Demo = () => (
    +  <div>
    +    <Button>普通按钮</Button>
    +    <TomatoButton>番茄色按钮</TomatoButton>
    +  </div>
    +);
    +
    +

    扩展样式改变标签 (as)

    + +
    const Button = styled.button`
    +  color: palevioletred;
    +  padding: 0.25em 1em;
    +  border: 2px solid palevioletred;
    +  border-radius: 3px;
    +  display: block;
    +`;
    +
    +const TomatoButton = styled(Button)`
    +  color: tomato;
    +  border-color: tomato;
    +`;
    +
    +const Demo = () => (
    +  <div>
    +    <Button>普通按钮</Button>
    +    <Button as="a" href="#">
    +      按钮样式的链接
    +    </Button>
    +    <TomatoButton as="a" href="#">
    +      番茄按钮样式的链接
    +    </TomatoButton>
    +  </div>
    +);
    +
    +

    自定义组件(as)

    + +
    const Button = styled.button`
    +  color: palevioletred;
    +  font-size: 1em;
    +  border: 2px solid palevioletred;
    +  display: block;
    +`;
    +
    +const ReversedButton = props => (
    +  <Button
    +    {...props}
    +    children={
    +      props.children.split('').reverse()
    +    }
    +  />
    +);
    +
    +render(
    +  <div>
    +    <Button>普通按钮</Button>
    +    <Button as={ReversedButton}>
    +      具有普通按钮样式的自定义按钮
    +    </Button>
    +  </div>
    +);
    +
    +

    样式化任何组件

    +
    const Link = ({ className, children }) => (
    +  <a className={className}>
    +    {children}
    +  </a>
    +);
    +const StyledLink = styled(Link)`
    +  color: palevioletred;
    +  font-weight: bold;
    +`;
    +<StyledLink className="hello" />
    +
    +

    在 render 之外定义 Styled 组件

    + +
    const Box = styled.div`/* ... */`;
    +const Wrapper = ({ message }) => {
    +  // ⚠️ 不能在这里定义 styled 组件
    +  return (
    +    <Box>
    +      {message}
    +    </Box>
    +  );
    +};
    +
    +

    注意:组件 Box 不能放到 Wrapper 函数组件里面

    +

    传入值

    +
    const Input = styled.input`
    +  color: ${
    +    props => 
    +      props.inputColor || "palevioletred"
    +  };
    +  background: papayawhip;
    +`;
    +const Demo = () => (
    +  <div>
    +    <Input
    +      defaultValue="@probablyup"
    +      type="text"
    +    />
    +    <Input
    +      defaultValue="@geelen"
    +      type="text"
    +      inputColor="rebeccapurple"
    +    />
    +  </div>
    +);
    +
    +

    样式对象

    +
    const PropsBox = styled.div(props => ({
    +  background: props.background,
    +  height: '50px',
    +  width: '50px',
    +  fontSize: '12px'
    +}));
    +
    +

    在组件中使用

    +
    const Example = () => {
    +  return (
    +    <div>
    +      <PropsBox
    +        background="blue"
    +      />
    +    </div>
    +  );
    +}
    +
    +

    注意:样式对象里面的样式并不是 CSS 中的写法。

    +

    CSSModules => styled

    + +
    import React, { useState } from 'react';
    +import styles from './styles.css';
    +
    +function ExampleCounter() {
    +  const [count, setCount] = useState(0)
    +  return (
    +    <div className={styles.counter}>
    +      <p className={styles.paragraph}>
    +        {count}
    +      </p>
    +      <button
    +        className={styles.button}
    +        onClick={() => setCount(count +1)}
    +      >
    +        +
    +      </button>
    +      <button
    +        className={styles.button}
    +        onClick={() => setCount(count -1)}
    +      >
    +        -
    +      </button>
    +    </div>
    +  );
    +}
    +
    +

    👇👇 与下面 styled 写法等效 👇👇

    +
    import styled from 'styled-components';
    +
    +const StyledCounter = styled.div`
    +  /* ... */
    +`;
    +const Paragraph = styled.p`
    +  /* ... */
    +`;
    +const Button = styled.button`
    +  /* ... */
    +`;
    +function ExampleCounter() {
    +  const [count, setCount] = useState(0);
    +  const increment = () => {
    +    setCount(count +1);
    +  }
    +  const decrement = () => {
    +    setCount(count -1);
    +  }
    +  return (
    +    <StyledCounter>
    +      <Paragraph>{count}</Paragraph>
    +      <Button onClick={increment}>
    +        +
    +      </Button>
    +      <Button onClick={decrement}>
    +        -
    +      </Button>
    +    </StyledCounter>
    +  );
    +}
    +
    +

    伪元素、伪选择器和嵌套

    + +
    const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))`
    +  color: blue;
    +  &:hover {              /* <Thing> 悬停时 */
    +    color: red;
    +  }
    +  & ~ & {                /* <Thing> 作为 <Thing> 的兄弟,但可能不直接在它旁边 */
    +    background: tomato;
    +  }
    +  & + & {                /* <Thing> 旁边的 <Thing> */
    +    background: lime;
    +  }
    +  &.something {          /* <Thing> 标记有一个额外的 CSS 类 “.something” */
    +    background: orange;
    +  }
    +  .something-else & {    /* <Thing> 在另一个标记为 “.something-else” 的元素中 */
    +    border: 1px solid;
    +  }
    +`;
    +
    +render(
    +  <React.Fragment>
    +    <Thing>Hello world!</Thing>
    +    <Thing>你怎么样?</Thing>
    +    <Thing className="something">
    +      艳阳高照...
    +    </Thing>
    +    <div>今天真是美好的一天。</div>
    +    <Thing>你不觉得吗?</Thing>
    +    <div className="something-else">
    +      <Thing>灿烂</Thing>
    +    </div>
    +  </React.Fragment>
    +);
    +
    +

    改变 styled 组件样式

    + +
    import { css } from 'styled-components'
    +import styled from 'styled-components'
    +
    +const Input = styled.input.attrs({
    +  type: "checkbox"
    +})``;
    +const LabelText = styled.span`
    +  ${(props) => {
    +    switch (props.$mode) {
    +      case "dark":
    +        return css`
    +          color: white;
    +          ${Input}:checked + && {
    +            color: blue;
    +          }
    +        `;
    +      default:
    +        return css`
    +          color: black;
    +          ${Input}:checked + && {
    +            color: red;
    +          }
    +        `;
    +    }
    +  }}
    +`;
    +
    +function Example() {
    +  return (
    +    <React.Fragment>
    +      <Label>
    +        <Input defaultChecked />
    +        <LabelText>Foo</LabelText>
    +      </Label>
    +      <Label>
    +        <Input />
    +        <LabelText $mode="dark">
    +          Foo
    +        </LabelText>
    +      </Label>
    +    </React.Fragment>
    +  );
    +}
    +
    +

    全局样式 createGlobalStyle

    +
    import {
    +  styled,
    +  createGlobalStyle
    +} from 'styled-components'
    +
    +const Thing = styled.div`
    +   && {
    +     color: blue;
    +   }
    +`;
    +const GlobalStyle = createGlobalStyle`
    +  div${Thing} {
    +    color: red;
    +  }
    +`;
    +
    +const Example = () => (
    +  <React.Fragment>
    +    <GlobalStyle />
    +    <Thing>
    +      我是蓝色的
    +    </Thing>
    +  </React.Fragment>
    +);
    +
    +

    className 使用

    +
    const Thing = styled.div`
    +  color: blue;
    +  /* <Thing> 中标记为“.something”的元素 */
    +  .something {
    +    border: 1px solid;
    +  }
    +`;
    +
    +function Example() {
    +  return (
    +    <Thing>
    +      <label
    +        htmlFor="foo-button"
    +        className="something"
    +      >
    +        神秘按钮
    +      </label>
    +      <button id="foo-button">
    +        我该怎么办?
    +      </button>
    +    </Thing>
    +  )
    +}
    +
    +

    共享样式片段

    +
    const rotate = keyframes`
    +  from {top:0px;}
    +  to {top:200px;}
    +`;
    +
    +// ❌ 这将引发错误!
    +const styles = `
    +  animation: ${rotate} 2s linear infinite;
    +`;
    +
    +// ✅ 这将按预期工作
    +const styles = css`
    +  animation: ${rotate} 2s linear infinite;
    +`;
    +
    +

    Class 组件样式定义

    +
    class NewHeader extends React.Component {
    +  render() {
    +    return (
    +      <div
    +        className={this.props.className}
    +      />
    +    );
    +  }
    +}
    +const StyledA = styled(NewHeader)``
    +const Box = styled.div`
    +  ${StyledA} {
    +    /* 变更 NewHeader 样式 */
    +  }
    +`;
    +
    +

    附加额外的 Props

    +
    const Input = styled.input.attrs(props=>({
    +  // 我们可以定义静态道具
    +  type: "text",
    +  // 或者我们可以定义动态的
    +  size: props.size || "1em",
    +}))`
    +  color: palevioletred;
    +  font-size: 1em;
    +  border: 2px solid palevioletred;
    +  border-radius: 3px;
    +
    +  /* 这里我们使用动态计算的 props */
    +  margin: ${props => props.size};
    +  padding: ${props => props.size};
    +`;
    +
    +

    使用 Input 组件

    +
    function Example() {
    +  return (
    +    <div>
    +      <Input placeholder="小文本输入" />
    +      <br />
    +      <Input
    +        placeholder="更大的文本输入"
    +        size="2em"
    +      />
    +    </div>
    +  )
    +}
    +
    +

    覆盖 .attrs

    +
    const Input = styled.input.attrs(props=>({
    +  type: "text",
    +  size: props.size || "1em",
    +}))`
    +  border: 2px solid palevioletred;
    +  margin: ${props => props.size};
    +  padding: ${props => props.size};
    +`;
    +// Input 的attrs会先被应用,然后这个 attrs obj
    +const PasswordInput = styled(Input).attrs({
    +  type: "password",
    +})`
    +  /* 同样,border 将覆盖 Input 的边框 */
    +  border: 2px solid aqua;
    +`;
    +
    +

    使用 InputPasswordInput 组件

    +
    render(
    +  <div>
    +    <Input
    +      placeholder="更大的文本输入"
    +      size="2em"
    +    />
    +    <br />
    +    {/*⚠️ 仍然可以使用Input中的 size attr*/}
    +    <PasswordInput
    +      placeholder="更大的密码输入"
    +      size="2em"
    +    />
    +  </div>
    +);
    +
    +

    动画

    +

    创建关键帧

    +
    const rotate = keyframes`
    +  from {
    +    transform: rotate(0deg);
    +  }
    +
    +  to {
    +    transform: rotate(360deg);
    +  }
    +`;
    +
    +

    我们创建一个 Rotate 组件

    +
    // 它将在两秒内旋转我们传递的所有内容
    +const Rotate = styled.div`
    +  display: inline-block;
    +  animation: ${rotate} 2s linear infinite;
    +  padding: 2rem 1rem;
    +  font-size: 1.2rem;
    +`;
    +
    +

    使用 Rotate 组件

    +
    function Example() {
    +  return (
    +    <Rotate>&lt; 💅🏾 &gt;</Rotate>
    +  )
    +}
    +
    +

    isStyledComponent

    + +
    import React from 'react'
    +import styled, { isStyledComponent } from 'styled-components'
    +import MaybeStyledComponent from './my'
    +
    +let TargetedComponent = isStyledComponent(MaybeStyledComponent)
    +  ? MaybeStyledComponent
    +  : styled(MaybeStyledComponent)``;
    +
    +const ParentComponent = styled.div`
    +  color: cornflowerblue;
    +
    +  ${TargetedComponent} {
    +    color: tomato;
    +  }
    +`;
    +
    +

    ThemeConsumer

    +
    import {
    +  ThemeConsumer
    +} from 'styled-components'
    +
    +function Example() {
    +  return (
    +    <ThemeConsumer>
    +      {theme => (
    +        <div>主题色是 {theme.color}</div>
    +      )}
    +    </ThemeConsumer>
    +  );
    +}
    +
    +

    TypeScript

    +

    安装

    +

    Web 应用上安装 styled

    +
    npm install -D @types/styled-components
    +
    +

    React Native 应用上安装 styled

    +
    npm install -D \
    +    @types/styled-components \
    +    @types/styled-components-react-native
    +
    +

    如果对 TypeScript 不熟悉,参考 TypeScript 备忘清单

    +

    自定义 Props

    +
    import styled from 'styled-components';
    +
    +interface TitleProps {
    +  readonly isActive: boolean;
    +}
    +
    +const Title = styled.h1<TitleProps>`
    +  color: ${(props) => (
    +    props.isActive 
    +      ? props.theme.colors.main 
    +      : props.theme.colors.secondary
    +  )};
    +`;
    +
    +

    简单的 Props 类型定义

    +
    import styled from 'styled-components';
    +import Header from './Header';
    +
    +const Header = styled.header`
    +  font-size: 12px;
    +`;
    +
    +const NewHeader = styled(Header)<{
    +  customColor: string;
    +}>`
    +  color: ${(props) => props.customColor};
    +`;
    +
    +

    禁止转移到子组件($)

    +
    import styled from 'styled-components';
    +import Header from './Header';
    +
    +interface ReHeader {
    +  $customColor: string;
    +}
    +
    +const ReHeader = styled(Header)<ReHeader>`
    +  color: ${
    +    props => props.$customColor
    +  };
    +`;
    +
    +

    禁止 customColor 属性转移到 Header 组件,在其前面加上美元($)符号

    +

    函数组件类型继承

    + +
    import { FC, PropsWithRef, DetailedHTMLProps, ImgHTMLAttributes } from 'react';
    +import styled from 'styled-components';
    +
    +const Img = styled.img`
    +  height: 32px;
    +  width: 32px;
    +`;
    +export interface ImageProps extends DetailedHTMLProps<
    +  ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement
    +> {
    +  text?: string;
    +};
    +export const Image: FC<PropsWithRef<ImageProps>> = (props) => (
    +  <Img src="" alt="" {...props} />
    +);
    +
    +

    React Native

    + +

    基础实例

    + +
    import React from 'react'
    +import styled from 'styled-components/native'
    +
    +const StyledView = styled.View`
    +  background-color: papayawhip;
    +`;
    +const StyledText = styled.Text`
    +  color: palevioletred;
    +`;
    +
    +class MyReactNativeComponent extends React.Component {
    +  render() {
    +    return (
    +      <StyledView>
    +        <StyledText>Hello World!</StyledText>
    +      </StyledView>
    +    );
    +  }
    +}
    +
    +

    React Native 中写 CSS

    + +
    import styled from 'styled-components/native'
    +
    +const RotatedBox = styled.View`
    +  transform: rotate(90deg);
    +  text-shadow-offset: 10px 5px;
    +  font-variant: small-caps;
    +  margin: 5px 7px 2px;
    +`;
    +
    +function Example() {
    +  return (
    +    <RotatedBox />
    +  )
    +}
    +
    +

    与 web 版本的一些区别是,您不能使用关键帧(keyframes)和 createGlobalStyle 助手,因为 React Native 不支持关键帧或全局样式。如果您使用媒体查询或嵌套 CSS,我们也会警告您。

    +

    高级用法

    + +

    主题化

    + +
    import styled, { ThemeProvider } from 'styled-components'
    +
    +// 定义我们的按钮,但这次使用 props.theme
    +const Button = styled.button`
    +  font-size: 1em;
    +  margin: 1em;
    +  padding: 0.25em 1em;
    +  border-radius: 3px;
    +
    +  /* 使用 theme.main 为边框和文本着色 */
    +  color: ${props => props.theme.main};
    +  border: 2px solid ${props => props.theme.main};
    +`;
    +
    +// 我们正在为未包装在 ThemeProvider 中的按钮传递默认主题
    +Button.defaultProps = {
    +  theme: {
    +    main: "palevioletred"
    +  }
    +}
    +
    +// 定义 props.theme 的外观
    +const theme = {
    +  main: "mediumseagreen"
    +};
    +
    +render(
    +  <div>
    +    <Button>Normal</Button>
    +
    +    <ThemeProvider theme={theme}>
    +      <Button>Themed</Button>
    +    </ThemeProvider>
    +  </div>
    +);
    +
    +

    功能主题

    + +
    import styled, { ThemeProvider } from 'styled-components'
    +
    +// 定义我们的按钮,但这次使用 props.theme
    +const Button = styled.button`
    +  color: ${props => props.theme.fg};
    +  border: 2px solid ${props => props.theme.fg};
    +  background: ${props => props.theme.bg};
    +
    +  font-size: 1em;
    +  margin: 1em;
    +  padding: 0.25em 1em;
    +  border-radius: 3px;
    +`;
    +// 在主题上定义我们的`fg`和`bg`
    +const theme = {
    +  fg: "palevioletred",
    +  bg: "white"
    +};
    +
    +// 这个主题交换了`fg`和`bg`
    +const invertTheme = ({ fg, bg }) => ({
    +  fg: bg,
    +  bg: fg
    +});
    +
    +render(
    +  <ThemeProvider theme={theme}>
    +    <div>
    +      <Button>默认主题</Button>
    +      <ThemeProvider theme={invertTheme}>
    +        <Button>反转主题</Button>
    +      </ThemeProvider>
    +    </div>
    +  </ThemeProvider>
    +);
    +
    +

    通过 withTheme 高阶组件

    + +
    import { withTheme } from 'styled-components'
    +
    +class MyComponent extends React.Component {
    +  render() {
    +    console.log('Current theme: ', this.props.theme)
    +    // ...
    +  }
    +}
    +
    +export default withTheme(MyComponent)
    +
    +

    useContext 钩子

    + +
    import { useContext } from 'react'
    +import { ThemeContext } from 'styled-components'
    +
    +const MyComponent = () => {
    +  const themeContext = useContext(ThemeContext)
    +  
    +  console.log('Current theme: ', themeContext)
    +  // ...
    +}
    +
    +

    useTheme 自定义钩子

    + +
    import {useTheme} from 'styled-components'
    +
    +const MyComponent = () => {
    +  const theme = useTheme()
    +
    +  console.log('Current theme: ', theme)
    +  // ...
    +}
    +
    +

    主题 props

    + +
    import {
    +  ThemeProvider,
    +  styled
    +} from 'styled-components';
    +
    +// 定义我们的按钮
    +const Button = styled.button`
    +  font-size: 1em;
    +  margin: 1em;
    +  padding: 0.25em 1em;
    +  /* 使用 theme.main 为边框和文本着色 */
    +  color: ${props => props.theme.main};
    +  border: 2px solid ${props => props.theme.main};
    +`;
    +// 定义主题的外观
    +const theme = {
    +  main: "mediumseagreen"
    +};
    +
    +

    使用自定义主题组件

    +
    render(
    +  <div>
    +    <Button theme={{ main: "royalblue" }}>
    +      特设主题
    +    </Button>
    +    <ThemeProvider theme={theme}>
    +      <div>
    +        <Button>Themed</Button>
    +        <Button
    +          theme={{ main: "darkorange" }}
    +        >
    +          被覆盖
    +        </Button>
    +      </div>
    +    </ThemeProvider>
    +  </div>
    +);
    +
    +

    Refs

    + +
    import {
    +  ThemeProvider,
    +  styled
    +} from 'styled-components';
    +
    +const Input = styled.input`
    +  border: none;
    +  border-radius: 3px;
    +`;
    +
    +class Form extends React.Component {
    +  constructor(props) {
    +    super(props);
    +    this.inputRef = React.createRef();
    +  }
    +
    +  render() {
    +    return (
    +      <Input
    +        ref={this.inputRef}
    +        placeholder="Hover to focus!"
    +        onMouseEnter={() => {
    +          this.inputRef.current.focus()
    +        }}
    +      />
    +    );
    +  }
    +}
    +
    +

    使用 Form 组件

    +
    function Example() {
    +  return (
    +    <Form />
    +  )
    +}
    +
    +

    特异性问题

    + +

    在文件 MyComponent.js 中定义 MyComponent 组件。

    +
    const MyComponent = styled.div`
    +  background-color: green;
    +`;
    +
    +

    定义样式 my-component.css

    +
    .red-bg {
    +  background-color: red;
    +}
    +
    +

    使用 MyComponent 组件

    +
    <MyComponent className="red-bg" />
    +
    +

    由于某种原因,这个组件仍然有绿色背景,即使你试图用 red-bg 类覆盖它!

    +

    解决方案

    +
    .red-bg.red-bg {
    +  background-color: red;
    +}
    +
    +

    ThemeProvider

    + +
    import styled, { ThemeProvider } from 'styled-components'
    +
    +const Box = styled.div`
    +  color: ${props => props.theme.color};
    +`;
    +
    +const Example = () => (
    +  <ThemeProvider theme={{ color: 'mediumseagreen' }}>
    +    <Box>I'm mediumseagreen!</Box>
    +  </ThemeProvider>
    +);
    +
    +

    shouldForwardProp

    + +
    const Comp = styled('div').withConfig({
    +  shouldForwardProp: (prop, defaultValidatorFn) =>
    +      !['hidden'].includes(prop) && defaultValidatorFn(prop),
    +}).attrs({ className: 'foo' })`
    +  color: red;
    +  &.foo {
    +    text-decoration: underline;
    +  }
    +`;
    +
    +const Example = () => (
    +  <Comp hidden draggable="true">
    +    Drag Me!
    +  </Comp>
    +);
    +
    +
    © 2022 Kenny Wang, All rights reserved.
    + diff --git a/index.html b/index.html index 7c77d2ed..80473ec3 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,10 @@ React + + + +Styled Components diff --git a/style/style.css b/style/style.css index dd6cfc87..24d7af90 100644 --- a/style/style.css +++ b/style/style.css @@ -212,7 +212,7 @@ table.show-header thead { } tt, code { - font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-family: ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,monospace; font-size: 1em; } pre:only-child { @@ -222,7 +222,7 @@ pre:only-child { pre { margin-top: 0; margin-bottom: 0; - font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-family: ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,monospace; word-wrap: normal; line-height: 1.5; overflow: hidden;