React高级指引
Fragments
<React.Fragment>类似于vue 中的<template>
,用来对子组件进行分组等操作,
有时,语义化的 HTML 会被破坏。比如当在 JSX 中使用 <div>
元素来实现 React 代码功能的时候,又或是在使用列表(<ol>
, <ul>
和 <dl>
)和 HTML <table>
时。 在这种情况下,我们应该使用 React Fragments 来组合各个组件。
将一个集合映射到一个 Fragments 数组 - 举个例子,创建一个描述列表:
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,React 会发出一个关键警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
引出
HTML
元素 (或 HTML 描述列表元素)是一个包含术语定义以及描述的列表,通常用于展示词汇表或者元数据 (键-值对列表)。
严格模式
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。
StrictMode
目前有助于:
在 CSS 中,如果你不希望节点成为布局的一部分,则可以使用
display: contents
属性。幂等,多次执行所产生的影响均与一次执行的影响相同
无障碍辅助功能
Accessibility,也被称为 a11y
for
在 JSX 中应该被写作htmlFor
代码分割
Create React App,Next.js,Gatsby
Webpack(代码分割)和 Browserify(factor-bundle)这类打包器支持的一项技术,能够创建多个包并在运行时动态加载。
React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该 Promise 需要 resolve 一个 defalut
export 的 React 组件。
Suspense
如果在父组件调用react.lazy加载子组件时,父组件加载完成子组件没有加载完成,可以调用Suspense组件进行优雅降级
你甚至可以用一个 Suspense
组件包裹多个懒加载组件
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
如果模块加载失败(如网络问题),它会触发一个错误。你可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
React.lazy
目前只支持默认导出(default exports)。如果你想被引入的模块使用命名导出(named exports),你可以创建一个中间模块,来重新导出为默认模块。这能保证 tree shaking 不会出错,并且不必引入不需要的组件。
错误边界
如果一个 class 组件中定义了 static getDerivedStateFromError()
或 componentDidCatch()
这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError()
渲染备用 UI ,使用 componentDidCatch()
打印错误信息。
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
注意
错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多)
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调函数)- 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
引出
声明式专注于我们所要实现的结果,不用去思考具体的过程
高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
被包装组件接收来自容器组件的所有 prop,同时也接收一个新的用于 render 的 data
prop。
HOC 不应该修改传入组件,而应该使用组合的方式,通过将组件包装在容器组件中实现功能
约定
-
将不相关的props传递给被包裹的组件
-
最大化可组合性
-
包装显示名称以轻松调试,recompose 提供了这个函数
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,HOC每次返回不同的组件,会导致组件树卸载之前的组件并重新渲染新组件。
-
务必复制静态方法
// 定义静态函数 WrappedComponent.staticMethod = function() {/*...*/} // 现在使用 HOC const EnhancedComponent = enhance(WrappedComponent); // 增强组件没有 staticMethod typeof EnhancedComponent.staticMethod === 'undefined' // true
-
refs不会被传递
引出
React 的 controller view 模式
深入 JSX
JSX 仅仅只是 React.createElement(component, props, ...children)
函数的语法糖。
使用JSX的注意事项:
-
由于 JSX 会编译为
React.createElement
调用形式,所以React
库也必须包含在 JSX 代码作用域内。import React from 'react'; import CustomButton from './CustomButton'; function WarningButton() { // return React.createElement(CustomButton, {color: 'red'}, null); return <CustomButton color="red" />; }
-
在 JSX 中,你也可以使用点语法来引用一个 React 组件。
import React from 'react'; const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />; }
-
用户定义的组件必须以大写字母开头,以小写字母开头的元素代表一个 HTML 内置组件
-
不能将通用表达式作为 React 元素类型。如果你想通过通用表达式来(动态)决定元素类型,你需要首先将它赋值给大写字母开头的变量。
jsx中的props:
-
props的默认值为true
-
以使用展开运算符
...
来在 JSX 中传递整个 props 对象。你还可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。const Button = props => { const { kind, ...other } = props; const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton"; return <button className={className} {...other} />; }; const App = () => { return ( <div> <Button kind="primary" onClick={() => console.log("clicked!")}> Hello World! </Button> </div> ); };
jsx中的子元素:
-
包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性
props.children
传递给外层组件。 -
布尔类型、Null 以及 Undefined 将会忽略。这有助于依据特定条件来渲染其他的 React 元素。值得注意的是有一些 “falsy” 值,如数字
0
,仍然会被 React 渲染。<div> {showHeader && <Header />} <Content /> </div>
Optimizing Performance
设置shouldComponentUpdate
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
可以使用类似“浅比较”(对于基本数据类型,比较值,对于引用数据类型,比较引用)的模式来检查 props
和 state
中所有的字段,以此来决定是否组件需要更新。继承 React.PureComponent
。
Portals
注意
对于模态对话框,通过遵循 WAI-ARIA 模态开发实践,来确保每个人都能够运用它。
当在使用 portal 时, 记住管理键盘焦点就变得尤为重要。
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
组件的挂载点虽然可以脱离父组件,但组件的事件通过冒泡机制仍可以传给父组件。
常用于对话框、悬浮卡以及提示框
Render Props
render prop 是一个用于告知组件需要渲染什么内容的函数 prop
用于在组件之间共享功能
重要的是要记住,render prop 是因为模式才被称为 render prop ,你不一定要用名为 render
的 prop 来使用这种模式。
注意
如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用
React.PureComponent
带来的优势。
Context
Context 提供了一种在组件之间共享值的方式,而不必显式地通过组件树的逐层传递 props。
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。