代码切割
懒加载 React.lazy
React.lazy 目前只支持默认导出
异常捕获边界
基于路由的代码分割
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法,共享数据。
何时使用 Context
目的:为了共享那些对于一个组件树而言是全局的数据
使用 context, 我们可以避免通过中间元素传递 props:
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext("light");
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
使用 Context 之前的考虑
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。
如果你只是想避免层层传递一些属性,组件组合有时候是一个比 context 更好的解决方案。
API
React.createContext
const MyContext = React.createContext(defaultValue);
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。
Context.Provider
<MyContext.Provider value={/* 某个值 */}>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。
Class.contextType(类组件中访问 context)
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
// 或者
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基于这个值进行渲染工作 */
}
}
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
Context.Consumer(函数组件中访问 context-在 hook 出现之前)
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。
错误边界
React 内部的 javascript 错误会导致整个 React 应用渲染失败
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
错误边界无法捕获的错误:
-
事件处理
-
异步代码
-
服务端渲染
-
它自身抛出来的错误
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
只有 class 组件才可以成为错误边界组件。
错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界
未捕获错误的新行为
任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
关于事件处理器
React 不需要错误边界来捕获事件处理器中的错误。与 render 方法和生命周期方法不同,事件处理器不会在渲染期间触发。因此,如果它们抛出异常,React 仍然能够知道需要在屏幕上显示什么。
如果你需要在事件处理器内部捕获错误,使用普通的 JavaScript try / catch 语句
Refs 转发(forwardRef)
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
这样,使用 FancyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
ref 的使用场景:
一般都是使用在我们需要在组件内部引用到原生的 DOM 节点来进行一些操作(仅限于组件本身)
forwardRef 的使用场景:
我们想访问到当前组件的子组件中的 DOM 元素时
Fragments
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
在 JSX 中,render 函数的返回值/函数组件的返回值必须只能有一个根节点,但是我们有不想使用额外的 DOM 元素来包裹我们返回的 JSX 时,就可以使用 Fragments,不会创建额外的 DOM 节点
<Fragments></Fragments>
// 或者
<></>
前者上面除了 key 属性不能有其他属性;或者上面不能添加任何属性。包括 key 属性
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
等价于
<table>
<tr>
<td>Hello</td>
<td>World</td>
</tr>
</table>
高阶组件
复用组件逻辑
高阶组件是参数为组件,返回值为新组件的函数。
HOC 不应该修改传入组件,而应该使用组合的方式,通过将组件包装在容器组件中实现功能
HOC 为组件添加特性。自身不应该大幅改变约定。HOC 返回的组件与原组件应保持类似的接口。
HOC 应该透传与自身无关的 props。大多数 HOC 都应该包含一个类似于下面的 render 方法:
render() {
// 过滤掉非此 HOC 额外的 props,且不要进行透传
const { extraProp, ...passThroughProps } = this.props;
// 将 props 注入到被包装的组件中。
// 通常为 state 的值或者实例方法。
const injectedProp = someStateOrInstanceMethod;
// 将 props 传递给被包装组件
return (
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
);
}
最常见的方式是用 HOC 包住被包装组件的显示名称。比如高阶组件名为 withSubscription,并且被包装组件的显示名称为 CommentList,显示名称应该为 WithSubscription(CommentList)
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {
/* ... */
}
WithSubscription.displayName = `WithSubscription(${getDisplayName(
WrappedComponent
)})`;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || "Component";
}
注意事项
不要在 render 方法中使用 HOC
React 的 diff 算法(称为协调)使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树。 如果从 render 返回的组件与前一个渲染中的组件相同(===),则 React 通过将子树与新子树进行区分来递归更新子树。 如果它们不相等,则完全卸载前一个子树。
对 HOC 来说这一点很重要,因为这代表着你不应在组件的 render 方法中对一个组件应用 HOC。
render() {
// 每次调用 render 函数都会创建一个新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
return <EnhancedComponent />;
}
推荐
const EnhancedComponent = enhance(MyComponent);
render() {
return <EnhancedComponent />;
}
这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失。
如果在组件之外创建 HOC,这样一来组件只会创建一次。因此,每次 render 时都会是同一个组件。一般来说,这跟你的预期表现是一致的。
务必复制静态方法
当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。
// 定义静态函数
WrappedComponent.staticMethod = function() {
/*...*/
};
// 现在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === "undefined"; // true
为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:
function enhance(WrappedComponent) {
class Enhance extends React.Component {
/*...*/
}
// 必须准确知道应该拷贝哪些方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
但要这样做,你需要知道哪些方法应该被拷贝。你可以使用 hoist-non-react-statics 自动拷贝所有非 React 静态方法:
import hoistNonReactStatic from "hoist-non-react-statics";
function enhance(WrappedComponent) {
class Enhance extends React.Component {
/*...*/
}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
Refs 不会被传递
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
这个问题的解决方案是通过使用 React.forwardRef。
与第三方库协同
React 不会理会 React 自身之外的 DOM 操作。
深入 JSX
JSX 是 React.createElement 的语法糖
React 必须在作用域内
由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。
如果你不使用 JavaScript 打包工具而是直接通过 <script>
标签加载 React,则必须将 React 挂载到全局变量中。
布尔类型、Null 以及 Undefined 将会忽略
false, null, undefined, and true 是合法的子元素。但它们并不会被渲染。
值得注意的是有一些 “falsy” 值,如数字 0,仍然会被 React 渲染。
要解决这个问题,确保 && 之前的表达式总是布尔值
如果你想渲染 false、true、null、undefined 等值,你需要先将它们转换为字符串
性能优化
shouldComponentUpdate(nextProps, nextState)
在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作。
在大部分情况下,你可以继承 React.PureComponent 以代替手写 shouldComponentUpdate()。它用当前与之前 props 和 state 的浅比较覆写了 shouldComponentUpdate() 的实现。
继承 React.PureComponent
Portals (Modal)
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container);
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
Profiler
Profiler 测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”。
声明默认属性
函数式组件和类组件都拥有 defaultProps 属性
class Greeting extends React.Component {
// ...
}
Greeting.defaultProps = {
name: "Mary"
};
协调
render 方法的返回值会创建一棵由 React 元素构成的树。在 state 或者是 props 发生改变的时候,相同的 render 方法会返回一棵不同的树。
React 需要基于这两棵树之间的差别来判断如何高效的更新 UI,以保证当前 UI 与最新的树保持同步。
React 在以下两个假设的基础之上提出了一套 O(n) 的启发式算法:
-
两个不同类型的元素会产生出不同的树;
-
开发者可以通过设置 key 属性,来告知渲染哪些子元素在不同的渲染下可以保存不变;
Diffing 算法
对比不同类型的元素
当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树(包括其所有的子节点)。(自定义组件比较的是引用,HTML 标签则比较的是标签名)
对比同一类型的元素
当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。
<div className="before" title="stuff" />
<div className="after" title="stuff" />
通过对比这两个元素,React 知道只需要修改 DOM 元素上的 className 属性。
当更新 style 属性时,React 仅更新有所更变的属性。比如:
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
通过对比这两个元素,React 知道只需要修改 DOM 元素上的 color 样式,无需修改 fontWeight。
在处理完当前节点之后,React 继续对子节点进行递归。
对比同类型的组件元素
当一个组件更新时,组件实例会保持不变,因此可以在不同的渲染时保持 state 一致。React 将更新该组件实例的 props 以保证与最新的元素保持一致
对子节点进行递归
默认情况下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。
在没有设置 key 的情况下:
-
在子元素列表末尾新增元素时,更新开销比较小。
-
如果只是简单的将新增元素插入到表头,那么更新开销会比较大,会重建每一个子元素
keys
为了解决上述问题,React 引入了 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。
refs & DOM
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
何时使用 ref
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
创建 ref
React.createRef() 创建的,并通过 ref 属性附加到 React 元素。
在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
访问 Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同:
-
当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
-
当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
-
你不能在函数组件上使用 ref 属性,因为他们没有实例。
例子:
- 为 DOM 元素添加 ref
以下代码使用 ref 去存储 DOM 节点的引用:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `textInput` 上
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
- 为 class 组件添加 ref
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return <CustomTextInput ref={this.textInput} />;
}
}
- Refs 与函数组件
默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例:
function MyFunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// This will *not* work!
return <MyFunctionComponent ref={this.textInput} />;
}
}
如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用),或者可以将该组件转化为 class 组件。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input type="text" ref={textInput} />
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}
将 DOM Refs 暴露给父组件
你可能希望在父组件中引用子节点的 DOM 节点
虽然你可以向子组件添加 ref,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数组件上无效。
这种情况下我们推荐使用 ref 转发。Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref。
回调 Refs
HTML 元素或者是组件可以给 ref 属性赋值一个函数,这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input type="text" ref={this.setTextInputRef} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return <CustomTextInput inputRef={el => (this.inputElement = el)} />;
}
}
Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点。对于 createRef 返回的对象,我们也可以进行类似的操作。
string 类型的 ref --- 过时,不推荐使用
<div ref="textInput">1111</div>
// 可以通过 this.refs.textInput 来访问 DOM 节点
关于回调 refs 的说明
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
render props
在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。
使用 Render Props 来解决横切关注点
组件是 React 代码复用的主要单元,但如何将一个组件封装的状态或行为共享给其他需要相同状态的组件并不总是显而易见。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img
src="/cat.jpg"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: "100vh" }} onMouseMove={this.handleMouseMove}>
{/*
使用 `render`prop 动态决定要渲染的内容,
而不是给出一个 <Mouse> 渲染结果的静态表示
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => <Cat mouse={mouse} />} />
</div>
);
}
}
render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
关于 render prop 一个有趣的事情是你可以使用带有 render prop 的常规组件来实现大多数高阶组件 (HOC)。
// 如果你出于某种原因真的想要 HOC,那么你可以轻松实现
// 使用具有 render prop 的普通组件创建一个!
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => <Component {...this.props} mouse={mouse} />} />
);
}
};
}
使用 Props 而非 render
render prop 是因为模式才被称为 render prop ,你不一定要用名为 render 的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”.
注意事项
将 Render Props 与 React.PureComponent 一起使用时要小心
如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。
为了绕过这一问题,有时你可以定义一个 prop 作为实例方法,类似这样:
class MouseTracker extends React.Component {
// 定义为实例方法,`this.renderTheCat`始终
// 当我们在渲染中使用它时,它指的是相同的函数
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
静态类型检查
TypeScript
npx create-react-app my-app --template typescript
文件后缀使用 .ts
或者是 .tsx
严格模式(没怎么使用过)
严格模式检查仅在开发模式下运行;它们不会影响生产构建。
使用<React.StrictMode></React.StrictMode>
来包裹我们的组件,则这些组件运行在严格模式下
作用:
-
识别不安全的生命周期
-
识别字符串的 ref
-
关于使用废弃的 findDOMNode 方法的警告
React 支持用 findDOMNode 来在给定 class 实例的情况下在树中搜索 DOM 节点。通常你不需要这样做,因为你可以将 ref 直接绑定到 DOM 节点
-
检测意外的副作用
-
检测过时的 context API
使用 PropTypes 进行类型检查
自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 库 代替。
React 也内置了有些类型检查功能,可以检查组件的 props 的类型
import PropTypes from "prop-types";
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Greeting.propTypes = {
name: PropTypes.string
};
当传入的 prop 值类型不正确时,JavaScript 控制台将会显示警告。出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
默认 Prop 值
可以通过配置特定的 defaultProps 属性来定义 props 的默认值
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 指定 props 的默认值: (函数式组件也可以通过函数名.propTypes/函数名.defaultProps来设置默认值和props类型检查)
Greeting.defaultProps = {
name: "Stranger"
};
// 渲染出 "Hello, Stranger":
ReactDOM.render(<Greeting />, document.getElementById("example"));
defaultProps 用于确保 this.props.name 在父组件没有 指定其值时,有一个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。
非受控组件
使用 ref 来从 DOM 节点中获取表单数据。
非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert("A name was submitted: " + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
默认值 defaultValue
在一个组件已经挂载之后去更新 defaultValue 属性的值,不会造成 DOM 上值的任何更新。
<input type="checkbox">
和 <input type="radio">
支持 defaultChecked,<input type="text">
, <select>
和 <textarea>
支持 defaultValue。
文件输入
<input type="file" />
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
Web Components
可以自由选择在 Web Components 中使用 React,或者在 React 中使用 Web Components,或者两者共存。
网友评论