创建应用
1 | npx create-react-app my-app |
react使用Apifox的项目案例,可参考Gitee/ad_manage_react
项目
json-server的使用,可参考全球新闻发布系统项目
单页面应用
与页面或后续页面的任何交互,都不再需要往返 server 加载资源,即页面不会重新加载
编译器
Babel 是 React 最常用的 compiler
打包工具
常用的打包 React 应用的工具有 webpack 和 Browserify。
管理工具
npm 和 Yarn 是两个常用的管理 React 应用依赖的 package 管理工具
JSX
React DOM 使用 camelCase(驼峰式命名)来定义属性的名称
组件
组件名称应该始终以大写字母开头(<Wrapper/>
而不是 <wrapper/>
)
props
props
是只读的。不应以任何方式修改它们
组合 vs 继承
props.children
每个组件都可以获取到 props.children
。它包含组件的开始标签和结束标签之间的内容
1 | <Welcome>Hello world!</Welcome> |
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children
,而是自行约定:将所需内容传入 props,并使用相应的 prop。
1 | function SplitPane(props) { |
在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:
1 | function Dialog(props) { |
1 | function Dialog(props) { |
state
当组件中的一些数据在某些时刻发生变化时,这时就需要使用 state
来跟踪状态
state
和 props
之间最重要的区别是:props
由父组件传入,而 state
由组件本身管理。组件不能修改 props
,但它可以修改 state
。
生命周期方法
受控组件 vs 非受控组件
Ref
状态提升
应该在 React 组件的哪个生命周期函数中发起 AJAX 请求?
在 componentDidMount
这个生命周期函数中发起 AJAX 请求
工具链
JSX 防止注入攻击
所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击
JSX 表示对象
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
1 | // 注意:这是简化过的结构 |
这些对象被称为 “React 元素”。
仅使用 React 构建的应用通常只有单一的根 DOM 节点。如果你在将 React 集成进一个已有应用,那么你可以在应用中包含任意多的独立根 DOM 节点。
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
组件
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。
1 | <Avatar user={props.author} /> |
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
不要直接修改 State
应该使用 setState()
构造函数是唯一可以给 this.state
赋值的地方
State 的更新可能是异步的
1 | // Wrong |
1 | // Correct |
条件渲染
元素变量
1 | {unreadMessages.length > 0 && |
与运算符 &&
true && expression
总是会返回 expression
, 而 false && expression
总是会返回 false
。
注意:下面示例中,render 方法的返回值是 <div>0</div>
1 | { count && <h1>Messages: {count}</h1>} |
三目运算符
1 | The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. |
1 | {isLoggedIn |
阻止组件渲染
让 render
方法直接返回 null
,而不进行任何渲染
受控组件
表单元素(如<input>
、 <textarea>
和 <select>
)通常自己维护 state
文件 input 标签
因为它的 value 只读,所以它是 React 中的一个非受控组件
处理多个输入
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
1 | handleInputChange(event) { |
等同 ES5:
1 | var partialState = {}; |
提交事件
1 | <form onSubmit={this.handleSubmit}> |
组合 vs 继承
包含关系
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar
(侧边栏)和 Dialog
(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中:
1 | {props.children} |
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children
,而是自行约定:将所需内容传入 props,并使用相应的 prop
1 | <div className="SplitPane"> |
将设计好的 UI 划分为组件层级
编写组件步骤
- 第一步:将设计好的 UI 划分为组件层级
- 第二步:用 React 创建一个静态版本
- 第三步:确定 UI state 的最小(且完整)表示
- 第四步:确定 state 放置的位置
- 第五步:添加反向数据流
通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
- 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
- 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
- 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
包含所有产品的原始列表是经由 props 传入的,所以它不是 state;搜索词和复选框的值应该是 state,因为它们随时间会发生改变且无法由其他数据计算而来;经过搜索筛选的产品列表不是 state,因为它的结果可以由产品的原始列表根据搜索词和复选框的选择计算出来。
综上所述,属于 state 的有:
- 用户输入的搜索词
- 复选框是否选中的值
哪个组件应该拥有某个 state
- 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
state 只能由拥有它们的组件进行更改
state在哪个组件,修改state的方法就在哪个组件
生命周期图谱
组件的生命周期
React权限菜单设计
(源码开放) React + webpack3 多页面应用 及 常见问题解答
10分钟快速搭建React权限菜单设计
React 组件权限控制的实现
React的React.FC与React.Component的初步认识
useMemo和useEffect有什么区别?怎么使用useMemo
useEffect
只能在DOM
更新后再触发再去控制
memo
是在DOM
更新前触发的,就像官方所说的,类比生命周期就是shouldComponentUpdate
在前端开发的过程中,我们需要缓存一些内容,以避免在需渲染过程中因大量不必要的耗时计算而导致的性能问题。为此 React 提供了一些方法可以帮助我们去实现数据的缓存,useMemo 就是其中之一
React Hooks
解释这个 Hook 之前先理解下什么是副作用。网络请求、订阅某个模块或者 DOM 操作都是副作用的例子,Effect Hook 是专门用来处理副作用的。正常情况下,在
Function Component
的函数体中,是不建议写副作用代码的,否则容易出 bug。
在绝大多数情况下,
useEffect
Hook 是更好的选择。唯一例外的就是需要根据新的 UI 来进行 DOM 操作的场景。useLayoutEffect
会保证在页面渲染前执行,也就是说页面渲染出来的是最终的效果。如果使用useEffect
,页面很可能因为渲染了 2 次而出现抖动。
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
1 | const ThemeContext = React.createContext('light'); |
当 Provider 的
value
值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于shouldComponentUpdate
函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
注意事项
因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染
为了防止这种情况,将 value 状态提升到父节点的 state 里:
1 | class App extends React.Component { |
useContext
1 | function HeaderBar() { |
useReducer
useReducer
的用法跟 Redux 非常相似,当 state 的计算逻辑比较复杂又或者需要根据以前的值来计算时,使用这个 Hook 比useState
会更好。
useCallback
1 | function Foo() { |
useCallback
缓存的是方法的引用,而useMemo
缓存的则是方法的返回值。使用场景是减少不必要的子组件渲染:
useRef
1 | function() { |
useRef
返回一个普通 JS 对象,可以将任意数据存到current
属性里面,就像使用实例化对象的this
一样。另外一个使用场景是获取 previous props 或 previous state
自定义 Hook
自定义 Hook 的命名有讲究,必须以
use
开头,在里面可以调用其它的 Hook。入参和返回值都可以根据需要自定义,没有特殊的约定。使用也像普通的函数调用一样,Hook 里面其它的 Hook(如useEffect
)会自动在合适的时候调用
Effect Hook
在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。(我们会在使用 Effect Hook 里展示对比useEffect
和这些方法的例子。)
Hook概览
1 | import React, { Suspense } from 'react'; |
Refs 转发
1 | const FancyButton = React.forwardRef((props, ref) => ( |
出于同样的原因,当
React.forwardRef
存在时有条件地使用它也是不推荐的:它改变了你的库的行为,并在升级 React 自身时破坏用户的应用。
高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
深入 JSX
if
语句以及for
循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用。但是,你可以用在 JSX 以外的代码中
字符串字面量
如下两个 JSX 表达式是等价的:
1 | <MyComponent message="hello world" /> |
当你将字符串字面量赋值给 prop 时,它的值是未转义的。所以,以下两个 JSX 表达式是等价的:
1 | <MyComponent message="<3" /> |
Props 默认值为 “True”
以下两个 JSX 表达式是等价的
1 | <MyTextBox autocomplete /> |
属性展开
以下两个组件是等价的:
1 | function App1() { |
你还可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。
1 | const Button = props => { |
字符串字面量
你可以将字符串放在开始和结束标签之间,此时 props.children
就只是该字符串。这对于很多内置的 HTML 元素很有用。例如:
1 | <MyComponent>Hello world!</MyComponent> |
React 组件也能够返回存储在数组中的一组元素:
1 | render() { |
JavaScript 表达式可以被包裹在 {}
中作为子元素。例如,以下表达式是等价的:
1 | <MyComponent>foo</MyComponent> |
布尔类型、Null 以及 Undefined 将会忽略
1 | <div> |
如果你想渲染 false
、true
、null
、undefined
等值,你需要先将它们转换为字符串:
1 | <div> |
性能优化
Brunch
通过安装 terser-brunch
插件,来获得最高效的 Brunch 生产构建:
避免调停
1 | shouldComponentUpdate(nextProps, nextState) { |
如果你知道在什么情况下你的组件不需要更新,你可以在 shouldComponentUpdate
中返回 false
来跳过整个渲染过程。其包括该组件的 render
调用以及之后的操作。
如果你的组件只有当 props.color
或者 state.count
的值改变才需要更新时,你可以使用 shouldComponentUpdate
来进行检查:
1 | shouldComponentUpdate(nextProps, nextState) { |
大部分情况下,你可以使用 React.PureComponent
来代替手写 shouldComponentUpdate
不可变数据的力量
1 | handleClick() { |
1 | handleClick() { |
为了不改变原本的对象,我们可以使用 Object.assign 方法:
1 | function updateColorMap(colormap) { |
添加对象扩展属性以使得更新不可变对象变得更方便:
1 | function updateColorMap(colormap) { |
Portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
1 | ReactDOM.createPortal(child, container) |
第一个参数(child
)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container
)是一个 DOM 元素。
1 | render() { |
一个 portal 的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:
Profiler
Profiler
测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”。 它的目的是识别出应用中渲染较慢的部分,或是可以使用类似 memoization 优化的部分,并从相关优化中获益。
不使用 ES6的使用方法
无论是函数组件还是 class 组件,都拥有 defaultProps
属性:
1 | Greeting.defaultProps = { |
如果使用 createReactClass()
方法创建组件,那就需要在组件中定义 getDefaultProps()
函数:
1 | getDefaultProps: function() { |
为了保险起见,以下三种做法都是可以的:
- 在 constructor 中绑定方法。
- 使用箭头函数,比如:
onClick={(e) => this.handleClick(e)}
。 - 继续使用
createReactClass
。
Render Props
1 | class Cat extends React.Component { |
1 | // 如果你出于某种原因真的想要 HOC,那么你可以轻松实现 |
render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
移动端事件使用onTouchMove
1 | onTouchMove={handleMouseMove} |
静态类型检查
建议在大型代码库中使用 Flow 或 TypeScript 来代替 PropTypes
。
flow
TypeScript
1 | npx create-react-app my-app --template typescript |
将 TypeScript 添加到现有的 Create React App 项目中,请参考此文档.
1 | yarn run tsc --init |
如果你使用 npm,执行:
1 | npx tsc --init |
tsconfig.json
文件中,有许多配置项用于配置编译器。查看所有配置项的的详细说明,请参考此文档。
1 | // tsconfig.json |
TypeScript React Starter 提供了一套默认的 tsconfig.json
帮助你快速上手。
在 React 中,你的组件文件大多数使用 .js
作为扩展名。在 TypeScript 中,提供两种文件扩展名:.ts
是默认的文件扩展名,而 .tsx
是一个用于包含 JSX
代码的特殊扩展名。
运行 TypeScript
如果你按照上面的说明操作,现在应该能运行 TypeScript 了。
1 | yarn build |
如果你使用 npm,执行:
1 | npm run build |
如果你没有看到输出信息,这意味着它编译成功了
严格模式
你可以为应用程序的任何部分启用严格模式。例如:
1 | import React from 'react'; |
StrictMode
目前有助于:
渲染阶段的生命周期包括以下 class 组件方法:
constructor
componentWillMount
(orUNSAFE_componentWillMount
)componentWillReceiveProps
(orUNSAFE_componentWillReceiveProps
)componentWillUpdate
(orUNSAFE_componentWillUpdate
)getDerivedStateFromProps
shouldComponentUpdate
render
setState
更新函数(第一个参数)
使用 PropTypes 进行类型检查
1 | import PropTypes from 'prop-types'; |
出于性能方面的考虑,propTypes
仅在开发模式下进行检查。
默认 Prop 值
1 | class Greeting extends React.Component { |
defaultProps
用于确保 this.props.name
在父组件没有指定其值时,有一个默认值。
非受控组件
1 | class NameForm extends React.Component { |
如果你还是不清楚在某个特殊场景中应该使用哪种组件,那么 这篇关于受控和非受控输入组件的文章 会很有帮助。
默认值
1 | <input |
希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue
属性,而不是 value
。
同样,<input type="checkbox">
和 <input type="radio">
支持 defaultChecked
,<select>
和 <textarea>
支持 defaultValue
。
文件输入
在 HTML 中,<input type="file">
可以让用户选择一个或多个文件上传到服务器,或者通过使用 File API 进行操作。
1 | <input type="file" /> |
在 React 中,<input type="file" />
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
1 | class FileInput extends React.Component { |
Web Components
React 顶层 API
转换元素
React
提供了几个用于操作元素的 API:
Fragments
React
还提供了用于减少不必要嵌套的组件。
Refs
Suspense
Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy
动态加载组件。它将在未来支持其它使用场景,如数据获取等。
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 拥有专属文档章节和单独的 API 参考文档:
React.PureComponent
React.PureComponent
与 React.Component
很相似。两者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
如果赋予 React 组件相同的 props 和 state,render()
函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent
可提高性能。
React.PureComponent
中的shouldComponentUpdate()
仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 props 和 state 较为简单时,才使用React.PureComponent
,或者在深层数据结构发生变化时调用forceUpdate()
来确保组件被正确地更新。你也可以考虑使用 immutable 对象加速嵌套数据的比较。
React.memo
1 | const MyComponent = React.memo(function MyComponent(props) { |
React.memo
为高阶组件。它与 React.PureComponent
非常相似,但只适用于函数组件,而不适用 class 组件。
1 | function MyComponent(props) { |
此方法仅作为**性能优化**的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。
createElement()
1 | React.createElement( |
cloneElement()
1 | React.cloneElement( |
isValidElement()
1 | React.isValidElement(object) |
验证对象是否为 React 元素,返回值为 true
或 false
。
React.Children
React.Children
提供了用于处理 this.props.children
不透明数据结构的实用方法。
React.createRef
React.forwardRef
React.lazy
1 | // 这个组件是动态加载的 |
React.Suspense
React.Suspense
可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。目前,懒加载组件是 <React.Suspense>
支持的唯一用例:
1 | // 该组件是动态加载的 |
React.Component
组件的生命周期
此方法仅作为**性能优化的方式**而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。
constructor()
1 | constructor(props) |
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
setState()
1 | this.setState((state, props) => { |
forceUpdate()
defaultProps
state
DOM 元素
合成事件
React 术语词汇表
Hook 简介
自定义 Hook
useState
1 | function Counter({initialCount}) { |
与 class 组件中的 setState
方法不同,useState
不会自动合并更新对象
1 | setState(prevState => { |
useEffect
清除 effect
1 | useEffect(() => { |
useLayoutEffect
Hook 来处理这类 effect。它和 useEffect
的结构相同,区别只是调用时机不同。
Hook 规则
只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook
自定义 Hook
Hook API 索引
useContext
即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
useReducer
1 | const [state, dispatch] = useReducer(reducer, initialArg, init); |
1 | 用 reducer 重写 useState 一节的计数器示例: |
惰性初始化
1 | function init(initialCount) { |
useCallback
每次渲染,函数会生成一个新的引用地址(引用类型,跟对象/数组一样)
- 保持一个函数的不变性
- 可以说是
useMemo
的一个语法糖,专门处理函数
下面两个代码是等价的:
useMemo
:
1 | const handleMegaBoost = React.useMemo(() => { |
useCallback
:
1 | const handleMegaBoost = React.useCallback(() => { |
1 | const memoizedCallback = useCallback( |
useMemo
Understanding useMemo and useCallback
- 做快照,减少大量运算
- 保持一个值的不变性
为了减少一些大量计算(需要计算的数据没有改变,但是其他数据改变,重新渲染,会引起大量计算)
useMemo
takes two arguments:(两个参数)
- A chunk of work to be performed, wrapped up in a function(大量计算的函数)
- A list of dependencies(依赖)
**useMemo
is essentially like a lil’ cache, and the dependencies are the cache invalidation strategy.**(像缓存)
This is commonly known as memoization, and it’s why this hook is called “useMemo”.(记忆化,备忘)
消耗性能的案例(每秒time变化,都会进行一次大量计算)
1 | import React, {useState, useEffect} from 'react' |
使用usememo之后,只有num变化时,才进行大量的计算
1 | import React, {useState, useEffect, useMemo} from 'react' |
I’ve extracted two new components, Clock
and PrimeCalculator
. By branching off from App
, these two components each manage their own state. A re-render in one component won’t affect the other.(可以将它们分成两个组件,各自管理它们自己的状态,互不影响)
React.memo
wraps around our component and protects it from unrelated updates. Our PurePrimeCalculator
will only re-render when it receives new data, or when its internal state changes.(可以使用 React.memo
包裹组件,让它成为一个纯组件【输入不改变的时候,输出不改变】)
改为独立组件
这样也是每秒都执行一次大量计算函数
1 | import React, {useState, useEffect} from 'react' |
解决:使用React.memo
将ShowInput组件改为纯函数
1 | import React, {useState, useEffect} from 'react' |
也可以在导出的时候,直接将其导出为纯组件
1 | // PrimeCalculator.js |
使用纯组件(React.memo)的方法,还是会进行大量计算
Boxes.js
1 | export default React.memo(Boxes); |
App.js
1 | const boxes = [ |
原因:every time React re-renders, we’re producing a brand new array. They’re equivalent in terms of value, but not in terms of reference.(组件每次重新渲染,会生成一个全新的数组,引用类型,地址就发生了改变,所以Boxes会重新渲染)
解决方法:使用useMemo
,依赖boxWidth
,当boxWidth
改变时才更新
1 | const boxes = React.useMemo(() => { |
当使用context时,父组件的值改变的时候,会引起子组件的更新。如果不想子组件每次都更新,可以使用useMemo
,只有当依赖user, status, forgotPwLink
发生改变时,再更新
1 | const AuthContext = React.createContext({}); |
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
Hooks FAQ
测试概览
AJAX and APIs
比如社区比较流行的 Axios,jQuery AJAX,或者是浏览器内置的 window.fetch。
在 componentDidMount
这个生命周期函数中发起 AJAX 请求
在组件中使用事件处理函数
Yehuda Katz 的文章详细解释了什么是绑定,以及函数在 JavaScript 中怎么起作用。
传递参数
1 | <button onClick={() => this.handleClick(id)} /> |
1 | <button onClick={this.handleClick.bind(this, id)} /> |
通过箭头函数传递参数
1 | <ul> |
通过 data-attributes 传递参数
1 | handleClick(e) { |
怎样阻止函数被调用太快或者太多次?
如果你有一个 onClick
或者 onScroll
这样的事件处理器,想要阻止回调被触发的太快,那么可以限制执行回调的速度,可以通过以下几种方式做到这点:
- 节流:基于时间的频率来进行抽样更改 (例如
_.throttle
) - 防抖:一段时间的不活动之后发布更改 (例如
_.debounce
) requestAnimationFrame
节流:基于 requestAnimationFrame 的抽样更改 (例如 raf-schd)
可以看这个比较 throttle 和 debounce 的可视化页面
节流
1 | import throttle from 'lodash.throttle'; |
防抖
防抖确保函数不会在上一次被调用之后一定量的时间内被执行。当必须进行一些费时的计算来响应快速派发的事件时(比如鼠标滚动或键盘事件时
),防抖是非常有用的。下面这个例子以 250ms 的延迟来改变文本输入。
1 | import debounce from 'lodash.debounce'; |