diff --git a/README.md b/README.md index 3b22cd7..8a8009e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Quick Reference [JavaScript](./docs/javascript.md) [JSON](./docs/json.md) [React](./docs/react.md) +[Styled Components](./docs/styled-components.md) [TOML](./docs/toml.md) [Markdown](./docs/markdown.md) [TypeScript](./docs/typescript.md) diff --git a/docs/docker.md b/docs/docker.md index 5869520..78998ae 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -25,12 +25,13 @@ $ docker run -d -p 80:80 docker/getting-started 在前台创建并运行容器 ```shell -$ docker run -it -p 8001:8080 --name my-nginx nginx +$ docker run -it -p --rm 8001:8080 --name my-nginx nginx ``` ---- - `-it` - 交互式 bash 模式 +- `--rm` - 容器终止运行后自动删除容器文件 - `-p 8001:8080` - 将 `8001` 端口映射到容器中的 `8080` 端口 - `--name my-nginx` - 指定名称 - `nginx` - 要使用的镜像 diff --git a/docs/jest.md b/docs/jest.md index 47fb735..2835a17 100644 --- a/docs/jest.md +++ b/docs/jest.md @@ -63,8 +63,6 @@ describe('My work', () => { }) ``` - - ### 测试结构 @@ -135,6 +133,97 @@ expect(value).toThrow(error) .toThrowErrorMatchingSnapshot() ``` +### 快照 + + +```js +expect(value) + .toMatchSnapshot() + .toMatchInlineSnapshot() +``` + +### Errors + + +```js +expect(value) + .toThrow(error) + .toThrowErrorMatchingSnapshot() +``` + +### Objects + + +```js +expect(value) + .toBeInstanceOf(Class) + .toMatchObject(object) + .toHaveProperty(keyPath, value) +``` + +### Objects + + +```js +expect(value) + .toContain(item) + .toContainEqual(item) + .toHaveLength(number) +``` + +### Numbers + + +```js +expect(value) + .toBeCloseTo(number, numDigits) + .toBeGreaterThan(number) + .toBeGreaterThanOrEqual(number) + .toBeLessThan(number) + .toBeLessThanOrEqual(number) +``` + +### Booleans + + +```js +expect(value) + .toBeFalsy() + .toBeNull() + .toBeTruthy() + .toBeUndefined() + .toBeDefined() +``` + +### Strings + + +```js +expect(value) + .toMatch(regexpOrString) +``` + +### NaN + + +```js +test('当值为 NaN 时通过', () => { + expect(NaN).toBeNaN(); + expect(1).not.toBeNaN(); +}); +``` + +### Others + + +```js +expect.extend(matchers) +expect.any(constructor) +expect.addSnapshotSerializer(serializer) + +expect.assertions(1) +``` + 匹配器 ---- @@ -388,8 +477,6 @@ expect(fn).toThrowErrorMatchingSnapshot() ### 实例 -请参阅 Jest 文档中的 [更多示例](https://jestjs.io/docs/en/tutorial-async)。 - 在异步测试中指定一些预期的断言是一个很好的做法,所以如果你的断言根本没有被调用,测试将会失败。 ```js @@ -409,6 +496,7 @@ beforeEach(expect.hasAssertions) ``` 这将验证每个测试用例至少存在一个断言。 它还可以与更具体的 `expect.assertions(3)` 声明配合使用。 +请参阅 Jest 文档中的 [更多示例](https://jestjs.io/docs/en/tutorial-async) ### async/await @@ -432,8 +520,8 @@ test('async test', (done) => { setTimeout(() => { try { - const result = getAsyncOperationResult() - expect(result).toBe(true) + const res = getAsyncOperatResult() + expect(res).toBe(true) done() } catch (err) { done.fail(err) @@ -469,18 +557,28 @@ 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' + ) }) ``` + 您还可以使用快照: ```js test('call the callback', () => { // mockName 在 Jest 22+ 中可用 - const callback = jest.fn().mockName('Unicorn') + const callback = jest.fn() + .mockName('Unicorn') + fn(callback) expect(callback).toMatchSnapshot() // -> @@ -506,6 +604,7 @@ const callback = jest.fn(() => true) ```js const callback = jest.fn().mockReturnValue(true) + const callbackOnce = jest.fn().mockReturnValueOnce(true) ``` @@ -513,26 +612,29 @@ const callbackOnce 或解析值: ```js -const promise +const promise = jest.fn().mockResolvedValue(true) -const promiseOnce + +const promiseOnce = jest.fn().mockResolvedValueOnce(true) ``` 他们甚至可以拒绝值: ```js -const failedPromise - = jest.fn().mockRejectedValue('Error') -const failedPromiseOnce - = jest.fn().mockRejectedValueOnce('Error') +const failedPromise = + jest.fn().mockRejectedValue('Error') + +const failedPromiseOnce = + jest.fn().mockRejectedValueOnce('Error') ``` 你甚至可以结合这些: ```js -const callback - = jest.fn().mockReturnValueOnce(false).mockReturnValue(true) +const callback = jest.fn() + .mockReturnValueOnce(false) + .mockReturnValue(true) // -> // call 1: false // call 2+: true @@ -541,13 +643,20 @@ const callback ### 使用 `jest.mock` 方法模拟模块 ```js -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](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options) - -> 注意:当使用 `babel-jest` 时,对 `jest.mock` 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。 +注意:当使用 `babel-jest` 时,对 [`jest.mock`](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options) 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。 ### 使用模拟文件模拟模块 @@ -563,22 +672,18 @@ module.exports = (a) => a jest.mock('lodash/memoize') ``` -注意:当使用 `babel-jest` 时,对 `jest.mock` 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。 +注意:当使用 `babel-jest` 时,对 `jest.mock` 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。[手动模拟文档](https://jestjs.io/docs/en/manual-mocks) -[手动模拟文档](https://jestjs.io/docs/en/manual-mocks) - -### 模拟对象方法 +### 模拟 getters 和 setters ```js -const spy = jest.spyOn(console, 'log').mockImplementation(() => {}) -expect(console.log.mock.calls).toEqual([['dope'], ['nope']]) -spy.mockRestore() -``` - -```js -const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true })) -expect(spy).toHaveBeenCalled() -spy.mockRestore() +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+) @@ -603,8 +708,9 @@ const setTitle = jest jest.useFakeTimers() test('kill the time', () => { const callback = jest.fn() - // 运行一些使用 setTimeout 或 setInterval 的代码 - const actual = someFunctionThatUseTimers(callback) + // 运行使用 setTimeout或setInterval 的代码 + const actual + = someFunctionThatUseTimers(callback) // 快进直到所有定时器都执行完毕 jest.runAllTimers() // 同步检查结果 @@ -619,8 +725,9 @@ test('kill the time', () => { 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) // 同步检查结果 @@ -628,20 +735,29 @@ test('kill the time', () => { }) ``` -对于特殊情况,请使用 [jest.runOnlyPendingTimers()](https://jestjs.io/docs/en/timer-mocks#run-pending-timers)。 +> 对于特殊情况,请使用 [jest.runOnlyPendingTimers()](https://jestjs.io/docs/en/timer-mocks#run-pending-timers)。 **注意:** 您应该在测试用例中调用 `jest.useFakeTimers()` 以使用其他假计时器方法。 -### 模拟 getters 和 setters +### 模拟对象方法 ```js -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() +``` + +```js +const spy = jest.spyOn(ajax, 'request') + .mockImplementation( + () => Promise.resolve({success: true}) + ) + +expect(spy).toHaveBeenCalled() +spy.mockRestore() ``` ### 清除和恢复模拟 @@ -653,8 +769,10 @@ Object.defineProperty(location, 'title', { // 清除模拟使用日期 // (fn.mock.calls、fn.mock.instances) fn.mockClear() + // 清除并删除任何模拟的返回值或实现 fn.mockReset() + // 重置并恢复初始实现 fn.mockRestore() ``` diff --git a/docs/package.json.md b/docs/package.json.md index 7c85977..6c247cb 100644 --- a/docs/package.json.md +++ b/docs/package.json.md @@ -101,7 +101,7 @@ https://registry.npmjs.org/[包名]/-/[包名]-[version].tgz 鼓励使用开源 [(OSI-approved)](https://opensource.org/licenses/alphabetical) 许可证,除非你有特别的原因不用它。 如果你开发的包是你工作的一部分,最好和公司讨论后再做决定。 -**license字段必须是以下之一:** +#### **license字段必须是以下之一** - 如果你使用标准的许可证,需要一个有效地 [SPDX 许可证标识](https://spdx.org/licenses/)。 - 如果你用多种标准许可证,需要有效的 [SPDX 许可证表达式2.0语法表达式](https://www.npmjs.com/package/spdx)。 diff --git a/docs/styled-components.md b/docs/styled-components.md new file mode 100644 index 0000000..7433096 --- /dev/null +++ b/docs/styled-components.md @@ -0,0 +1,1110 @@ +styled-components 备忘清单 +==== + +此快速参考备忘单提供了使用 CSS in JS 工具的各种方法。 + + +入门 +---- + +### 安装 + +[Styled Components](https://styled-components.com) 是增强 CSS 在 React 组件系统样式的 CSS in JS 的最佳实践。 + +- [VSCode styled-components](https://github.com/styled-components/vscode-styled-components) _有代码高亮和代码提示_ +- [VIM styled-components](https://github.com/styled-components/vim-styled-components) _有代码高亮_ +- [WebStorm styled-components](https://github.com/styled-components/webstorm-styled-components) _有代码高亮和代码提示_ + +安装依赖和 TypeScript 类型依赖 + +```bash +npm install --save styled-components +``` + +### 快速开始 + + +```jsx +import styled from 'styled-components'; +``` + +创建一个 Title 组件 +```jsx +// 该组件将呈现具有样式的

标签 +const Title = styled.h1` + font-size: 1.5em; + text-align: center; +`; +``` + +创建一个 Wrapper 组件 + +```jsx +// 该组件将呈现具有某些样式的
标记 +const Wrapper = styled.section` + padding: 4em; + background: papayawhip; +`; +``` + +像使用其他 React 组件一样使用 Title/Wrapper - 除了它们的样式! + +```jsx +function Demo() { + return ( + + + Hello World! + + + ); +} +``` + +### 根据 Props 适配 + + +```jsx +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 控制按钮样式 + +```jsx {5} +function Demo() { + return ( +
+ + +
+ ); +} +``` + +### 扩展样式 + +```jsx {7} +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 = () => ( +
+ + 番茄色按钮 +
+); +``` + +### 扩展样式改变标签 (as) + + +```jsx {17,20} +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 = () => ( +
+ + + + 番茄按钮样式的链接 + +
+); +``` + +### 自定义组件(as) + + +```jsx {20} +const Button = styled.button` + color: palevioletred; + font-size: 1em; + border: 2px solid palevioletred; + display: block; +`; + +const ReversedButton = props => ( + + + +); +``` + +### 样式化任何组件 + +```jsx +const Link = ({ className, children }) => ( + + {children} + +); +const StyledLink = styled(Link)` + color: palevioletred; + font-weight: bold; +`; + +``` + +### 在 render 之外定义 Styled 组件 + + +```jsx {3} +const Box = styled.div`/* ... */`; +const Wrapper = ({ message }) => { + // ⚠️ 不能在这里定义 styled 组件 + return ( + + {message} + + ); +}; +``` + +注意:组件 `Box` 不能放到 `Wrapper` 函数组件里面 + +### 传入值 + +```jsx {3,4,17} +const Input = styled.input` + color: ${ + props => + props.inputColor || "palevioletred" + }; + background: papayawhip; +`; +const Demo = () => ( +
+ + +
+); +``` + + +### 样式对象 + +```jsx {2,5} +const PropsBox = styled.div(props => ({ + background: props.background, + height: '50px', + width: '50px', + fontSize: '12px' +})); +``` + +在组件中使用 + +```jsx {5} +const Example = () => { + return ( +
+ +
+ ); +} +``` + +注意:样式对象里面的样式并不是 CSS 中的写法。 + +### CSSModules => styled + + +```jsx +import React, { useState } from 'react'; +import styles from './styles.css'; + +function ExampleCounter() { + const [count, setCount] = useState(0) + return ( +
+

+ {count} +

+ + +
+ ); +} +``` + +#### 👇👇 与下面 **styled** 写法等效 👇👇 + +```jsx +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 ( + + {count} + + + + ); +} +``` + +### 伪元素、伪选择器和嵌套 + + +```jsx {3,6,9,12,15} +const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))` + color: blue; + &:hover { /* 悬停时 */ + color: red; + } + & ~ & { /* 作为 的兄弟,但可能不直接在它旁边 */ + background: tomato; + } + & + & { /* 旁边的 */ + background: lime; + } + &.something { /* 标记有一个额外的 CSS 类 “.something” */ + background: orange; + } + .something-else & { /* 在另一个标记为 “.something-else” 的元素中 */ + border: 1px solid; + } +`; + +render( + + Hello world! + 你怎么样? + + 艳阳高照... + +
今天真是美好的一天。
+ 你不觉得吗? +
+ 灿烂 +
+
+); +``` + +### 改变 styled 组件样式 + + +```jsx {13,21} +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 ( + + + + + ); +} +``` + +### 全局样式 createGlobalStyle + +```jsx {3,11} +import { + styled, + createGlobalStyle +} from 'styled-components' + +const Thing = styled.div` + && { + color: blue; + } +`; +const GlobalStyle = createGlobalStyle` + div${Thing} { + color: red; + } +`; + +const Example = () => ( + + + + 我是蓝色的 + + +); +``` + +### className 使用 + +```JSX +const Thing = styled.div` + color: blue; + /* 中标记为“.something”的元素 */ + .something { + border: 1px solid; + } +`; + +function Example() { + return ( + + + + + ) +} +``` + +### 共享样式片段 + +```jsx +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 组件样式定义 + +```jsx {5} +class NewHeader extends React.Component { + render() { + return ( +
+ ); + } +} +const StyledA = styled(NewHeader)`` +const Box = styled.div` + ${StyledA} { + /* 变更 NewHeader 样式 */ + } +`; +``` + +### 附加额外的 Props + +```jsx {3,5,13,14,23} +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` 组件 + +```jsx +function Example() { + return ( +
+ +
+ +
+ ) +} +``` + +### 覆盖 .attrs + +```jsx {11} +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; +`; +``` + +使用 `Input` 和 `PasswordInput` 组件 + +```jsx {5,11} +render( +
+ +
+ {/*⚠️ 仍然可以使用Input中的 size attr*/} + +
+); +``` + +### 动画 + +创建关键帧 + +```jsx +const rotate = keyframes` + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +`; +``` + +我们创建一个 `Rotate` 组件 + +```jsx +// 它将在两秒内旋转我们传递的所有内容 +const Rotate = styled.div` + display: inline-block; + animation: ${rotate} 2s linear infinite; + padding: 2rem 1rem; + font-size: 1.2rem; +`; +``` + +使用 `Rotate` 组件 + +```jsx +function Example() { + return ( + < 💅🏾 > + ) +} +``` + +### isStyledComponent + + +```jsx +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 + +```jsx +import { + ThemeConsumer +} from 'styled-components' + +function Example() { + return ( + + {theme => ( +
主题色是 {theme.color}
+ )} +
+ ); +} +``` + +TypeScript +---- + +### 安装 + +Web 应用上安装 styled + +```bash +npm install -D @types/styled-components +``` + +React Native 应用上安装 styled + +```bash +npm install -D \ + @types/styled-components \ + @types/styled-components-react-native +``` + +如果对 TypeScript 不熟悉,参考 [TypeScript 备忘清单](./typescript.md) + +### 自定义 Props + +```tsx +import styled from 'styled-components'; + +interface TitleProps { + readonly isActive: boolean; +} + +const Title = styled.h1` + color: ${(props) => ( + props.isActive + ? props.theme.colors.main + : props.theme.colors.secondary + )}; +`; +``` + +### 简单的 Props 类型定义 + +```tsx +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}; +`; +``` + +### 禁止转移到子组件($) + +```tsx {5} +import styled from 'styled-components'; +import Header from './Header'; + +interface ReHeader { + $customColor: string; +} + +const ReHeader = styled(Header)` + color: ${ + props => props.$customColor + }; +`; +``` + +禁止 `customColor` 属性转移到 `Header` 组件,在其前面加上美元(`$`)符号 + +### 函数组件类型继承 + + +```tsx {8,13} +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 +> { + text?: string; +}; +export const Image: FC> = (props) => ( + +); +``` + +React Native +---- + + +### 基础实例 + + +```jsx +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 ( + + Hello World! + + ); + } +} +``` + +### React Native 中写 CSS + + +```jsx +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 ( + + ) +} +``` + +与 web 版本的一些区别是,您不能使用关键帧(`keyframes`)和 `createGlobalStyle` 助手,因为 React Native 不支持关键帧或全局样式。如果您使用媒体查询或嵌套 CSS,我们也会警告您。 + +高级用法 +---- + + +### 主题化 + + +```jsx +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( +
+ + + + + +
+); +``` + +### 功能主题 + + +```jsx +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( + +
+ + + + +
+
+); +``` + +### 通过 withTheme 高阶组件 + + +```jsx +import { withTheme } from 'styled-components' + +class MyComponent extends React.Component { + render() { + console.log('Current theme: ', this.props.theme) + // ... + } +} + +export default withTheme(MyComponent) +``` + +### useContext 钩子 + + +```jsx +import { useContext } from 'react' +import { ThemeContext } from 'styled-components' + +const MyComponent = () => { + const themeContext = useContext(ThemeContext) + + console.log('Current theme: ', themeContext) + // ... +} +``` + +### useTheme 自定义钩子 + + +```jsx +import {useTheme} from 'styled-components' + +const MyComponent = () => { + const theme = useTheme() + + console.log('Current theme: ', theme) + // ... +} +``` + +### 主题 props + + +```jsx +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" +}; +``` + +使用自定义主题组件 + +```jsx +render( +
+ + +
+ + +
+
+
+); +``` + +### Refs + + +```jsx +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 ( + { + this.inputRef.current.focus() + }} + /> + ); + } +} +``` + +使用 `Form` 组件 + +```jsx +function Example() { + return ( +
+ ) +} +``` + +### 特异性问题 + + +在文件 `MyComponent.js` 中定义 `MyComponent` 组件。 + +```jsx +const MyComponent = styled.div` + background-color: green; +`; +``` + +定义样式 `my-component.css` + +```css +.red-bg { + background-color: red; +} +``` + +使用 `MyComponent` 组件 + +```jsx + +``` + +由于某种原因,这个组件仍然有绿色背景,即使你试图用 `red-bg` 类覆盖它! + +#### 解决方案 + +```css +.red-bg.red-bg { + background-color: red; +} +``` + +### ThemeProvider + + +```jsx +import styled, { ThemeProvider } from 'styled-components' + +const Box = styled.div` + color: ${props => props.theme.color}; +`; + +const Example = () => ( + + I'm mediumseagreen! + +); +``` + +### shouldForwardProp + + +```jsx +const Comp = styled('div').withConfig({ + shouldForwardProp: (prop, defaultValidatorFn) => + !['hidden'].includes(prop) && defaultValidatorFn(prop), +}).attrs({ className: 'foo' })` + color: red; + &.foo { + text-decoration: underline; + } +`; + +const Example = () => ( + +); +``` \ No newline at end of file diff --git a/scripts/assets/styled-components.svg b/scripts/assets/styled-components.svg new file mode 100644 index 0000000..5e15a08 --- /dev/null +++ b/scripts/assets/styled-components.svg @@ -0,0 +1,3 @@ + + + diff --git a/scripts/style.css b/scripts/style.css index dd6cfc8..24d7af9 100644 --- a/scripts/style.css +++ b/scripts/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;