React学习之路(4):代码分割&Context

一、代码分割

这里介绍React中常用的几种代码分割的方法:

1、动态import()

没使用动态import之前:

import {add} from './math'
useadd(){
	console.log(add(16, 26));
}

使用动态import后:

useadd(){
	import('./math').then(math=>{
		console.log(add(16, 26));
	})
}

两者的区别是:没使用动态import时,在编译阶段就引入了math模块,而在使用了动态import时候,只有在调用useadd函数的时候才会去引入math模块,这里实际上是对不同模块做了懒加载,只在需要的时候加载代码块,加快应用整体的响应时间,提高用户体验。

2、React.lazy

React.lazy 和 Suspense一般是配合使用。React.lazy用于动态引入组件,实现组件的懒加载,将不同模块的代码分开打包。在Suspense组件下渲染lazy组件的时候,我们可以在Suspense组件的fallback属性中定义等待lazy组件加载时的UI,增加应用友好度。

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

3、路由代码分割

在路由切换阶段渲染页面时的一点加载时间是用户可以容忍的,所以在路由这个层面做代码分割也是一个好主意。通过React.lazy与React Router可以配置基于路由的代码分割。

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>
);

在上面的例子中,我们在Route中实现了路由的懒加载,当url为“/”时程序才会去载入相对应的Home组件,当url为“/about”时程序才会去载入相对应的About组件。在代码打包阶段,会自动识别这种路由懒加载的方法,并将相对应的组件代码分别打包,按需加载。

4、命名导出

前面大家也看到,React.lazy方法中都是使用默认导出的形式,因为目前这个方法只支持默认导出。如果想使用命名导出的话可以通过创建一个中间模块的方法来做转换,保证最终懒加载时还是使用默认导出,例子如下:

// ManyComponents.js
export const MyComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
const MyComponent = lazy(() => import("./MyComponent.js"));

二、Context

之前的学习博客中提到过,React中可以使用props向子组件传递所需要的数据。但是如果一个React应用它里面的组件嵌套地很深的话,顶层应用传给底层应用的prop就不得不在它们之间的每个组件进行传递,这种做法是十分不友好的,有几个缺点:
1、当在底层应用中看到一个不知道具体内容的prop时,需要顺着组件树一层一层地往上找。
2、当这个React应用中间需要添加新的组件时,这个新的组件也要接过父组件传过来的prop,增大了代码的工作量。

为了解决这个问题,React中提供了Context来在组件中共享某些值,比如认证的用户、应用的主题等,不必显式通过prop逐层传递props。

通过React.createContext()可以创建一个Context对象,可以向其传入一个默认值,当订阅了这个对象的组件在组件树中找不到可用的context值时,会使用这个默认的值。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

const MyContext = React.createContext(defaultValue);

这里介绍一下生产者(Provider)与消费者(Consumer)的概念:
每个Context对象会返回一个Provider组件与Consumer组件。Provider接收一个value作为context值,用于传递给消费者,它允许消费组件订阅这个context的变化,也就是说,当Provider的value值发生变化时,这个Provider包含的所有消费组件都会重新更新渲染。而Consumer需要嵌套在Provider下面,它可以通过回调的方式拿到共享的context。

<Provider value={/*共享数据*/}>
	<Consumer>
  		{value => /*根据拿到的context执行相关操作*/}
	</Consumer>
</Provider>

可以将class上的contextType属性赋值给一个context对象,这样可以在组件的任何生命周期使用this.context访问到最近的context值。

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
  }
  render() {
    let value = this.context;
  }
}
MyClass.contextType = MyContext;

前面提到,当Provider的value值更新时,会重新渲染它下面的所有消费组件,但是也可以在一个嵌套的很深的组件中来更新context。这可以通过使用context来传递一个更新函数实现:

class App extends React.Component {
  constructor(props) {
    super(props);
	//这里定义了一个更新函数,主要用于改变context值
    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
    // State包含了更新函数,因此它会被传递进 context provider中。
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }
  render() {
    // 整个 state 都被传递进 provider,当在组件中调用了value传入的更新函数,会更新value中的theme值,从而对该Provider组件下面的消费组件重新进行渲染
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

当需要多个context值时,将每个context单独分割成一个节点可以加快context更新引发的重渲染

      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>

这里还有一个需要注意的点,就是当将对象作为Provider组件的value值传入的时候,要将这个对象提升到父节点的state中。因为如果直接在value中定义了一个对象,每次父组件重新渲染的时候,value属性会赋值给一个新的对象,这样会导致下面的消费组件全部重渲染,浪费性能!

错误的做法:

<Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>

正确的做法:

<Provider value={this.state.value}>
        <Toolbar />
      </Provider>

顺便记录一下一种无需context来向底层组件传递顶层组件参数的办法,就是不传参数,而是传底层组件。中间的组件无需知道参数在底层组件中是以哪种方式存在的,它们只负责传递该组件,最后通过组合组件的方式渲染到相应的底层位置!(不得不说官网的例子还是挺好理解的!

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}

上述这些都是笔者在官网学习过程中的总结与一些个人的思考,觉得有帮助的可以点个赞哈哈哈~~

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值