React

react新属性

2020-08-23  本文已影响0人  Lia代码猪崽

Context

Context提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递。

但这种类似全局变量的方式,会让组件失去独立性,复用起来比较麻烦。

API

使用

  1. 引用
import React, { createContext } from 'React';
  1. 声明一个Context

注意一定要大写开头,因为会当做一个组件来使用。

const TestContext = createContext(0);
  1. 在父组件中使用TestContext组件,且定义为生产者Provider,用value属性传值。
<TestContext.Provider value={10}>
</TestContext.Provider>
  1. 在子组件中使用TestContext组件,且定义为消费者Consumer,通过箭头函数来获取传过来的值。

Context.Consumer
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue

<TestContext.Consumer>
  {
    test => <h1>test:{test}</h1>
  }
 </TestContext.Consumer>

来一个完整demo代码:

有三层组件嵌套:
父App -> 子Middle -> 孙Leaf。
在父App组件中,把state里的test值,通过Context的生产者传给所有子元素。
在孙Leaf组件中,通过Context的消费者拿到了从祖先元素传过来的test值。且每一次在祖先元素的修改,孙组件的视图也会刷新。

App.jsx:

import React, { createContext, useState } from 'react';
import './App.css';

const TestContext = createContext(0);

function Middle() {
  return (
      <div>
        <Leaf></Leaf>
      </div>
  )
}

function Leaf() {
  return (
      <div>
        <TestContext.Consumer>
          {
            test => <h1>test:{test}</h1>
          }
        </TestContext.Consumer>
      </div>
  )
}

function App() {
  const [test, setTest] = useState(0);

  return (
    <div className="App">
      <TestContext.Provider value={test}>
        <Middle/>
      </TestContext.Provider>
      <button onClick={ () => { setTest(test + 1) }  }>点击我给test的值加1</button>
    </div>
  );
}

export default App;

Class.contextType

背景

由于Consumer的特性,里面的JSX语句,必须是函数的返回值,这样的代码会有累赘,所以contextType登场了~

作用

静态属性contextType访问跨层级组件的数据。

使用

  1. 父组件要传递过来的数据要通过Context.Provider提供,此处省略代码
  2. contextType是一个类静态变量,所以使用static关键字来声明它
static contextType = TestContext;
  1. 经过步骤2的声明后,在render函数里,我们就可以通过this.context来获取到父组件传递过来的值(实际为test),为方便后续使用,赋值给变量test
const test = this.context;
  1. 要用直接引用test即可,不需要再用Consumer以及它的返回函数
class Leaf extends Component {
  static contextType = TestContext;

  render() {
    const test = this.context;
    return (
        <div>
          <h1>test:{test}</h1>
        </div>
    )
  }
}

完整项目代码

App.jsx

import React, { Component, createContext } from 'react';
import './App.css';

const TestContext = createContext(0);

class Middle extends Component {
  render() {
    return <Leaf />
  }
}

class Leaf extends Component {
  static contextType = TestContext;

  render() {
    const test = this.context;
    return (
        <div>
          <h1>test:{test}</h1>
        </div>
    )
  }
}

class App extends Component {
  state = {
    test: 0,
  };

  render() {
    const { test } = this.state;
    return (
        <div className="App">
          <TestContext.Provider value={test}>
            <Middle/>
          </TestContext.Provider>
          <button onClick={ () => { this.setState({test: test + 1}) }  }>点击我给test的值加1</button>
        </div>
    )
  }
}

export default App;

lazy 和 Suspense实现延迟加载(懒加载)

注意:这封装的是组件的导入行为,而不是组件本身

使用

  1. 引用lazy
import React, { Component, lazy } from 'react';
  1. 使用lazy函数来封装导入组件
const About = lazy(() => import('./components/About'));
  1. 引入suspense
import React, { Component, lazy, Suspense } from 'react';
  1. 给需要异步加载的组件外包裹上Suspense,且设置Suspense组件的fallback属性

fallback属性:
设置加载过程中需要显示的信息。
但需要注意要传入的是组件实例,如<Loading/>,而不能为组件名,如Loading

class App extends Component {
  render() {
    return (
        <div>
          <Suspense fallback={<div>Loading</div>}>
            <About/>
          </Suspense>
        </div>
    )
  }
}
  1. 可以修改这chunk的名字
    可以在控制台的NetWork中看到,这模块是异步加载的。所以可以通过webpack语法来修改这个chunk的名字。
const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));
image.png
  1. 处理加载异常
    方法一,使用componentDidCatch()生命周期函数
import React, { Component, lazy, Suspense } from 'react';

const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));

class App extends Component {
  state = {
    hasError: false
  };

  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
    this.setState({
      hasError: true
    })
  }

  render() {
    const { hasError } = this.state;
    if (hasError) {
      return <h1>有错误!</h1>
    }

    return (
        <div>
          <Suspense fallback={<div>Loading</div>}>
            <About/>
          </Suspense>
        </div>
    )
  }
}

export default App;

方法二,使用静态方法getDerivedStateFromError(),该方法的返回值会修改state

import React, { Component, lazy, Suspense } from 'react';

const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));

class App extends Component {
  state = {
    hasError: false
  };

  static getDerivedStateFromError() {
    return {
      hasError: true
    }
  }

  render() {
    const { hasError } = this.state;
    if (hasError) {
      return <h1>有错误!</h1>
    }

    return (
        <div>
          <Suspense fallback={<div>Loading</div>}>
            <About/>
          </Suspense>
        </div>
    )
  }
}

export default App;

memo

背景

当我们在父App组件中,引入了子Foo组件时,但父组件的某些属性发生了改变,App的视图会刷新,而哪怕属性值与Foo子组件无关,子组件也会跟随刷新,这不是我们想要的,消耗了性能。

Class的生命周期函数shouldComponentUpdate

通过shouldComponentUpdate(nextProps, nextState, nextContext),我们可以在将要刷新之前拿到组件的相关数据,然后再函数里边通过对比,如果不想要刷新视图的场景,返回false即可。

import React, { Component } from 'react';

class Foo extends Component {
  shouldComponentUpdate(nextProps, nextState, nextContext) {
    if (nextProps.name === this.props.name) {
      return false;
    }
    return true;
  }

  render() {
    console.log('Foo~')
    return null;
  }
}

class App extends Component {
  state = {
    count: 0
  };

  render() {
    const { count } = this.state;

    return (
        <div>
          <h1>{ count }</h1>
          <button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
          <Foo name="foo"/>
        </div>
    )
  }
}

export default App;

PureComponent

PureComponent其实就相当于帮我们实现了一个shouldComponentUpdate(~)函数,但有局限性,只能判断最外层的属性值,不可以判断对象中的属性或者函数

import React, { Component, PureComponent } from 'react';

class Foo extends PureComponent {

  render() {
    console.log('Foo~')
    return null;
  }
}

class App extends Component {
  state = {
    count: 0
  };

  render() {
    const { count } = this.state;

    return (
        <div>
          <h1>{ count }</h1>
          <button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
          <Foo name="foo"/>
        </div>
    )
  }
}

export default App;

Memo用法

无状态组件可以通过函数的形式来定义。
函数组件就无法继承PureComponent了,而memo,就相当于是PureComponent,可用于函数声明的组件。

  1. 引入memo
import React, { Component, memo } from 'react';
  1. 使用memo函数包裹函数组件
const Foo = memo(function Foo() {
  console.log('Foo~')
  return null;
})

完整代码

import React, { Component, memo } from 'react';

const Foo = memo(function Foo() {
  console.log('Foo~')
  return null;
})

class App extends Component {
  state = {
    count: 0
  };

  render() {
    const { count } = this.state;

    return (
        <div>
          <h1>{ count }</h1>
          <button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
          <Foo name="foo"/>
        </div>
    )
  }
}

export default App;

那么像PureComponent、memo是用什么算法来判断是否需要重新计算和执行?是使用===吗?还是其他?

答案:使用的是shallowEqual 算法

shallowEqual 算法在简单类型上使用了 Object.is,但是对于引用类型,shallowEqual 会做多一层的比较,比如两个对象{a: 1}{a: 1},用 Object.is 判断肯定是不相等的,但是对于 shallowEqual,它们则是等价的,对于数组的判断也类似。
参考:https://coding.imooc.com/learn/questiondetail/121369.html
补充:shallowEqual.js源码

上一篇 下一篇

猜你喜欢

热点阅读