大多数 React
应用都会使用 webpack
这类的构建工具来打包文件,打包是一个将文件引入并合并到一个单独文件的过程,最终形成一个 “bundle”
;接着在页面引入该 bundle
,整个应用即可以一次性加载。
Webpack
支持代码分割,打包的时候可以创建多个包并在运行时动态加载。
对应用进行代码分割虽然不能帮助我们减少应用整体的代码体积,但是可以避免加载用户不需要的代码,并在初始加载的时候减少所需加载的代码量。能够帮助我们“懒加载” 当前用户所需要的内容,能够显著提高应用性能。
代码分割的方式:
使用前:
import { add } from './math';
console.log(add(16, 26));
使用后:
import("./math").then(math => {
console.log(math.add(16, 26));
});
在 Suspense
组件中渲染 lazy
组件,如此使得我们可以使用在等待加载 lazy
组件时做优雅降级(如 loading 指示器等)。React.lazy
定义一个动态加载的组件,有助于缩减bundle的体积,延迟加载未使用的组件。
fallback
属性接受任何组件加载过程中你想展示的React元素
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
渐进增强(Progressive Enhancement):一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
优雅降级(Graceful Degradation):一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
如果模块加载失败(比如说:网络问题),就会触发问题,可以通过异常捕获边界技术来处理这些问题。
import React, { Suspense } from 'react';
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 Router
来进行代码分割
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>
);
Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法,不需要自伤而下逐层传递props
。
使用context
,可以避免通过中间元素传递props
;
当React渲染一个订阅了context对象的组件,这个组件就会从组件树中离自身最近的那个匹配的Provider
中读取到当前的 context
值。
当Provider 的value
值发生变化时,它内部的所有消费组件都会重新渲染;
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context对象(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
// ⚠️ 这里,没有使用默认值“light”,而是使用了“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() {
let value = this.context; // 基于这个value值进行渲染工作
return <Button theme={value} />;
}
}
Context
主要应用场景在于很多不同层级的组件需要访问同样一些数据;但是一定要谨慎使用,因为这会使得组件的复用性变差。
如果只是想避免层层传递一些属性,组件组合有时候是一个比 context
更好的解决方案。
theme-context.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
themes.dark // 默认值
);
themed-button.js
import {ThemeContext} from './theme-context';
class ThemedButton extends React.Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
可以通过context传递一个函数,使得consumers 组件更新context;
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// Theme Toggler 按钮不仅仅只获取 theme 值,
// 它也从 context 中获取到一个 toggleTheme 函数
return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;