目录
1. 无障碍辅助功能
跳过,后面再补。。。
2. 代码分割
打包:
- 将多个文件引入合并到一个单独文件中的过程。接着在页面上引入这个打包文件,整个应用即可一次性加载。
- 但是如果文件体积过大,就会导致加载时间过长。
- 代码分割:创建多个包,在运行时动态加载。
代码分割方式:
- 最佳方式是通过动态
import()
语法
使用// 使用之前 import { add } from './math'; console.log(add(16, 26)); // 使用之后 import("./math").then(math => { console.log(math.add(16, 26)); });
Create React App
或Next.js
,该功能已开箱即用,无需配置。
自己使用webpack
: webpack代码分离
webpack配置示例
使用Babel
,要使它能够解析动态import
语法而不是将其进行转换,需使用babel-plugin-syntax-dynamic-import插件 React.lazy
React.lazy
函数能让你像渲染常规组件一样处理动态引入(的组件)。React.lazy
和Suspense
技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库
import React, { Suspense } from 'react'; // 模块加载失败(如网络问题),它会触发一个错误 // 通过异常捕获边界(Error boundaries)技术来处理这些情况 import MyErrorBoundary from './MyErrorBoundary'; // React.lazy 接受一个函数,这个函数需要动态调用 import()。 // 它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。 const OtherComponent = React.lazy(() => import('./OtherComponent')); const AnotherComponent = React.lazy(() => import('./AnotherComponent')); function MyComponent() { return ( <div> <MyErrorBoundary> // fallback 属性接受任何在组件加载过程中你想展示的 React 元素。 // 你可以将 Suspense 组件置于懒加载组件之上的任何位置。 // 你甚至可以用一个 Suspense 组件包裹多个懒加载组件。 <Suspense fallback={ <div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </MyErrorBoundary> </div> ); }
- 基于路由的代码分割
import React, { Suspense, lazy } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={ <div>Loading...</div>}> <Switch> <Route exact path="/" component={ Home}/> <Route path="/about" component={ About}/> </Switch> </Suspense> </Router> );
命名导出: React.lazy
目前只支持默认导出(default exports
)。如果你想被引入的模块使用命名导出(named exports
),你可以创建一个中间模块,来重新导出为默认模块。
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export {
MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, {
lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
3. Context
Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。
Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
// 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
的解决方案是将内部组件自身传递下去,因而中间组件无需知道内部组件的所需的传参,只有最顶层的组件才知道。
API
-
React.createContext
const MyContext = React.createContext(defaultValue);
- 创建一个
Context
对象。当React
渲染一个订阅了这个Context
对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider
中读取到当前的context
值。 - 只有当组件所处的树中没有匹配到
Provider
时,其defaultValue
参数才会生效。这有助于在不使用Provider
包装组件的情况下对组件进行测试。 - 注意:将
undefined
传递给Provider
的value
时,消费组件的defaultValue
不会生效。
- 创建一个
-
Context.Provider
<MyContext.Provider value={ /* 某个值 */}>
- 每个
Context
对象都会返回一个Provider React
组件,它允许消费组件订阅context
的变化。 Provider
接收一个value
属性,传递给消费组件。一个Provider
可以和多个消费组件有对应关系。多个Provider
也可以嵌套使用,里层的会覆盖外层的数据。- 当
Provider
的value
值发生变化时,它内部的所有消费组件都会重新渲染。Provider
及其内部consumer
组件都不受制于shouldComponentUpdate
函数,因此当consumer
组件在其祖先组件退出更新的情况下也能更新。
- 每个
-
Class.contextType
挂载在class
上的contextType
属性会被重赋值为一个由React.createContext()
创建的Context
对象。这能让你使用this.context
来消费最近Context
上的那个值。你可以在任何生命周期中访问到它,包括render
函数中。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;
实验性的
public class fields
语法,你可以使用static
这个类属性来初始化你的contextType
。class MyClass extends React.Component { static contextType = MyContext; render() { let value = this.context; /* 基于这个值进行渲染工作 */ } }
-
Context.Consumer
<MyContext.Consumer> { value => /* 基于 context 值进行渲染*/} </MyContext.Consumer>
- 这里,
React
组件也可以订阅到context
变更。这能让你在函数式组件中完成订阅context
。 - 这需要函数作为子元素(function as a child)这种做法。这个函数接收当前的
context
值,返回一个React
节点。传递给函数的value
值等同于往上组件树离这个context
最近的Provider
提供的value
值。如果没有对应的Provider
,value
参数等同于传递给createContext()
的defaultValue
。
- 这里,
-
Context.displayName
context
对象接受一个名为displayName
的property
,类型为字符串。React DevTools
使用该字符串来确定context
要显示的内容。const MyContext = React.createContext(/* some value */); MyContext.displayName = 'MyDisplayName'; <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中 <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
4. 错误边界
错误边界是一种 React
组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript
错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
注意:
错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多),可以使用普通的
JavaScript try / catch
语句 - 异步代码(例如
setTimeout
或requestAnimationFrame
回调函数) - 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
如果一个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>
注意:
- 注意错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于
JavaScript
中catch {}
的工作机制。 - 自
React 16
起,任何未被错误边界捕获的错误将会导致整个React
组件树被卸载。
5. Refs 转发
- Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
- Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。
- Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。
使用方法:
createRef
class App extends React.Component{
constructor(props) {
super(props);
// 一般在构造函数中将refs分配给实例属性,以供组件的其它方法中使用
this.myRef = React.createRef();
console.log(this.myRef)
}
componentDidMount(){
this.myRef.current.someMethod() // 也就是说,可以允许父组件调用子组件的方法
}
render() {
return <Children ref={
this.myRef} />;
}
}
class Children extends React.Component{
someMethod(){
console.log(123)
}
render() {
return <span>123</span>;
}
}
-
回调Refs
React 将在组件挂载时,会调用ref
回调函数并传入DOM
元素,当卸载时调用它并传入null
。在componentDidMount
或componentDidUpdate
触发前,React
会保证refs
一定是最新的。class App extends React.Component{ constructor(props) { super(props); this.targetRef = null this.myRef = (e)=> this.targetRef = e; } componentDidMount(){ if(this.targetRef){ this.targetRef.innerHTML = '123' } } render() { return <div ref={ this.myRef} />; } }
-
String
类型的Refs
(不推荐使用) -
useRef (React Hooks)
import { useRef } from 'react'; function RefExample(props){ const inputElement = useRef() return(<div> <input ref={ inputElement}></input> <button onClick={ ()=>{ inputElement.current.focus() }}>focus</button> </div>) }
-
useRef
vscreateRef
createRef
每次渲染都会返回一个新的引用,而useRef
每次都会返回相同的引用。function RefExample(props){ const counterUseRef = useRef() const counterCreateRef = createRef() const [counter, setcounter] = useState(0) if(!counterUseRef.current){ counterUseRef.current = counter } if(!counterCreateRef.current){ counterCreateRef.current = counter } return(<div> <div>{ counter}</div> <div>{ counterUseRef.current}</div> <div>{ counterCreateRef.current}</div> <div onClick={ ()=>{ setcounter(counter + 1) }}>add</div> </div>) }
forwardRef
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ ref} className="FancyButton"> { props.children} </button> )); // You can now get a ref directly to the DOM button: const ref = React.createRef(); <FancyButton ref={ ref}>Click me!</FancyButton>;
使用场景:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
总而言之,都是需要获取到DOM实例的场景
能使用react数据流完成的操作,尽量不依赖ref
6. Fragments
React
中的一个常见模式是一个组件返回多个元素。Fragments
允许你将子列表分组,而无需向 DOM
添加额外节点。
作用类似vue里的<template>
// ...
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// ...
// 简写 注意:不支持 key 或属性。
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
// ...
7. 高阶组件
- 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
- HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的
设计模式
。 - 具体而言,
高阶组件是参数为组件,返回值为新组件的函数
。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
注意:
- HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
- 不要改变原始组件。使用组合。
- 约定:将不相关的 props 传递给被包裹的组件
- 约定:包装显示名称以便轻松调试
- 不要在 render 方法中使用 HOC
- 务必复制静态方法
- Refs 不会被传递
8. 与第三方库协同
React 可以被用于任何 web 应用中。它可以被嵌入到其他应用,且需要注意,其他的应用也可以被嵌入到 React。
9. 深入 JSX
实际上,JSX
仅仅只是 React.createElement(component, props, ...children)
函数的语法糖。
10. 性能优化
11. Portals
Portal
提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container)
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。
- 第二个参数(container)是一个 DOM 元素。
portal
的典型用例:
当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。
例如,对话框、悬浮卡以及提示框;
12. Profiler API
Profiler
测量渲染一个 React
应用多久渲染一次以及渲染一次的“代价”。
它的目的是识别出应用中渲染较慢的部分,或是可以使用类似 memoization
优化的部分,并从相关优化中获益。
注意:Profiling
增加了额外的开支,所以它在生产构建中会被禁用。
render(
<App>
<Profiler id="Navigation" onRender={callback}>
<Navigation {...props} />
</Profiler>
<Profiler id="Main" onRender={callback}>
<Main {...props} />
</Profiler>
</App>
);
Profiler
能添加在 React
树中的任何地方来测量树中这部分渲染所带来的开销。
它需要两个prop
:
一个是id(string)
,
一个是当组件树中的组件“提交”更新的时候被React
调用的回调函数 onRender(function)
。