摘要:接收一个属性,这个组件会让后代组件统一提供这个变量值。因此对于同一个对象而言,一定是后代元素。解决方法就是把内联函数提取出来,如下讲了这么多,我们还没有讲到其实我们已经讲完了的工作原理了。
本节主要讲解以下几个新的特性:
Context
ContextType
lazy
Suspense
错误边界(Error boundaries)
memo
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!
Context定义:Context 提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递。
这定义读的有点晦涩,来看张图:
假设有如上的组件层级关系,如果最底层的 Item 组件,需要最顶层的 Window 组件中的变量,那我们只能一层一层的传递下去。非常的繁琐,最重要的是中间层可能不需要这些变量。
有了 Context 之后,我们传递变量的方式是这样的:
Item 可以直接从 Window 中获取变量值。
当然这种方式会让组件失去独立性,复用起来更困难。不过存在即合理,一定有 Context 适用场景。那 Context 是如何工作的呢。
首先要有一个 Context 实例对象,这个对象可以派生出两个 React 组件,分别是 Provier 和 Consumer。
Provider 接收一个 value 属性,这个组件会让后代组件统一提供这个变量值。当然后代组件不能直接获取这个变量,因为没有途径。所以就衍生出 Consumer 组件,用来接收 Provier 提供的值。
一个 Provider 可以和多个消费组件有对应关系。多个 Consumer 也可以嵌套使用,里层的会覆盖外层的数据。
因此对于同一个 Context 对象而言,Consumer 一定是 Provier 后代元素。
创建 Contect 方式如下:
const MyContext = React.createContext(defaultValue?);
来个实例:
import React, {createContext, Component} from "react"; const BatteryContext = createContext(); class Leaf extends Component { render() { return ({ battery => ); } } // 为了体现层级多的关系,增加一层 Middle 组件 class Middle extends Component { render() { returnBattery: {battery}
}} } class App extends Component { render () { return ( ) } } export default App;
上述,首先创建一个 Context 对象 BatteryContext, 在 BatteryContext.Provider 组件中渲染 Middle 组件,为了说明一开始我们所说的多层组件关系,所以我们在 Middle 组件内不直接使用 BatteryContext.Consumer。而是在 其内部在渲染 Leaf 组件,在 Leaf 组件内使用 BatteryContext.Consumer 获取BatteryContext.Provider 传递过来的 value 值。
运行结果:
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
来个实例:
... class App extends Component { state = { battery: 60 } render () { const {battery} = this.state; return () } } ...
首先在 App 中的 state 内声明一个 battery 并将其传递给 BatteryContext.Provider 组件,通过 button 的点击事件进减少 一 操作。
运行效果 :
同样,一个组件可能会消费多个 context,来演示一下:
import React, {createContext, Component} from "react"; const BatteryContext = createContext(); const OnlineContext = createContext(); class Leaf extends Component { render() { return ({ battery => ( ); } } // 为了体现层级多的关系,增加一层 Middle 组件 class Middle extends Component { render() { return{ online => ) }Battery: {battery}, Online: {String(online)}
}} } class App extends Component { state = { online: false, battery: 60 } render () { const {battery, online} = this.state; console.log("render") return ( ) } } export default App;
同 BatteryContext 一样,我们在声明一个 OnlineContext,并在 App state 中声明一个 online 变量,在 render 中解析出 online。如果有多个 Context 的话,只要把对应的 Provier 嵌套进来即可,顺序并不重要。同样也加个 button 来切换 online 的值。
接着就是使用 Consumer,与 Provier 一样嵌套即可,顺序一样不重要,由于 Consumer 需要声明函数,语法稍微复杂些。
运行结果:
接下来在 App 中注释掉
//
在看运行效果:
可以看出,并没有报错,只是 battery 取不到值。这时候 createContext() 的默认值就派上用场了,用以下方式创建:
const BatteryContext = createContext(90);
这个默认值的使用场景就是在 Consumer 找不到 Provier 的时候。当然一般业务是不会有这种场景的。
ContextType... class Leaf extends Component { render() { return ({ battery => ); } } ...Battery: {battery}
}
回到一开始的实例,我们在看下 Consuer 里面的实现。由于 Consumer 特性,里面的 JSX 必须是该 Consumer 的回返值。这样的代码就显得有点复杂。我们希望在整个 JSX 渲染之前就能获取 battery 的值。所以 ContextType 就派上用场了。这是一个静态变量,如下:
... class Leaf extends Component { static contextType = BatteryContext; render() { const battery = this.context; return (Battery: {battery}
); } } ...
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
你只通过该 API 订阅单一 context。如果你想订阅多个,就只能用较复杂的写法了。lazy 和 Supense 的使用
React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。
首先声明一个 About 组件
import React, {Component} from "react" export default class About extends Component { render () { returnAbout} }
然后在 APP 中使用 lazy 动态导入 About 组件:
import React, {Component, lazy, Suspense} from "react" const About = lazy(() => import(/*webpackChunkName: "about" */"./About.jsx")) class App extends Component { render() { return (); } } export default App;
运行后会发现:
因为 App 渲染完成后,包含 About 的模块还没有被加载完成,React 不知道当前的 About 该显示什么。我们可以使用加载指示器为此组件做优雅降级。这里我们使用 Suspense 组件来解决。
只需将异步组件 About 包裹起来即可。
...Loading...
fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个异步组件。
那如果 about 组件加载失败会发生什么呢?
上面我们使用 webpackChunkName 导入的名加载的时候取个一个名字 about,我们看下网络请求,右键点击 Block Request URL
重新加载页面后,会发现整个页面都报错了:
在实际业务开发中,我们肯定不能忽略这种场景,怎么办呢?
错误边界(Error boundaries)如果模块加载失败(如网络问题),它会触发一个错误。你可以通过错误边界技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
接着,借用错误边界,我们来优化以上当异步组件加载失败的情况:
class App extends Component { state = { hasError: false, } static getDerivedStateFromError(e) { return { hasError: true }; } render() { if (this.state.hasError) { returnerror} return (}>Loading...
运行效果:
memo先来看个例子:
class Foo extends Component { render () { console.log("Foo render"); return null; } } class App extends Component { state = { count: 0 } render() { return (); } }
例子很简单声明一个 Foo 组件,并在 APP 的 state 中声明一个变量 count ,然后通过按钮更改 count 的值。
运行结果:
可以看出 count 值每变化一次, Foo 组件都会重新渲染一次,即使它没有必要重新渲染,这个是我们的可以优化点。
React 中提供了一个 shouldComponentUpdate,如果这个函数返回 false,就不会重新渲染。在 Foo 组件中,这里判断只要传入的 name 属性没有变化,就表示不用重新渲染。
class Foo extends Component { ... shouldComponentUpdate (nextProps, nextState) { if (nextProps.name === this.props.name) { return false } return true } ... }
运行效果:
Foo 组件不会重新渲染了。但如果我们传入数据有好多个层级,我们得一个一个的对比,显然就会很繁琐且冗长。 其实 React 已经帮我们提供了现层的对比逻辑就是 PureComponent 组件。我们让 Foo 组件继承 PureComponent
... class Foo extends PureComponent { render () { console.log("Foo render"); return null; } } ...
运行效果同上。但它的实现还是有局限性的,只有传入属性本身的对比,属性的内部发生了变化,它就搞不定了。来个粟子:
class Foo extends PureComponent { render () { console.log("Foo render"); return{this.props.person.age}; } } class App extends Component { state = { person: { count: 0, age: 1 } } render() { const {person} = this.state; return (); } }
在 App 中声明一个 person,通过点击按钮更改 person 中的age属性,并把 person 传递给 Foo 组件,在 Foo 组件中显示 age。
运行效果:
点击按键后,本应该重新渲染的 Foo 组件,却没有重新渲染。就是因为 PureComponent 提供的 shouldComponentUpdate 发现的 person 本身没有变化,才拒绝重新渲染。
所以一定要注意 PureComponent 使用的场景。只有传入的 props 第一级发生变化,才会触发重新渲染。所以要注意这种关系,不然容易发生视图不渲染的 bug。
PureComponent 还有一个陷阱,修改一下上面的例子,把 age 的修改换成对 count,然后在 Foo 组件上加一个回调函数:
... return (); ...{}}/>
运行效果:
可以看到 Foo 组件每次都会重新渲染,虽然 person 本身没有变化,但是传入的内联函数每次都是新的。
解决方法就是把内联函数提取出来,如下:
... callBack = () => {}...
讲了这么多,我们还没有讲到 memo,其实我们已经讲完了 memo 的工作原理了。
React.memo 为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。
我们 Foo 组件并没有相关的状态,所以可以用函数组件来表示。
... function Foo (props) { console.log("Foo render"); return{props.person.age}; } ...
接着使用 memo 来优化 Foo 组件
... const Foo = memo(function Foo (props) { console.log("Foo render"); return{props.person.age}; }) ...
运行效果
最后,如果你喜欢这个系列的,肯请大家给个赞的,我将会更有的动力坚持写下去。
参考React 官方文档
《React劲爆新特性Hooks 重构去哪儿网》
交流干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
https://github.com/qq44924588...
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/104610.html
摘要:粟例说明一下获取子组件或者节点的句柄指向已挂载到上的文本输入元素本质上,就像是可以在其属性中保存一个可变值的盒子。粟例说明一下渲染周期之间的共享数据的存储上述使用声明两个副作用,第一个每隔一秒对加,因为只需执行一次,所以每二个参为空数组。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! React 新特性讲解及实例(一) React 新特性 Hooks 讲解及实...
摘要:来个使用类形式的例子以上就不说解释了,第一篇已经讲过了,接着将改成用的形式接着使用形式接收一个对象的返回值并返回该的当前值。如果的返回值是函数的话,那么就可以简写成的方式,只是简写而已,实际并没有区别。 本文是 React 系列的第三篇 React 新特性讲解及实例(一) React 新特性 Hooks 讲解及实例(二) 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着...
摘要:还可以返回另一个回调函数,这个函数的执行时机很重要。对于第二个我们可以通过返回一个回调函数来注销事件的注册。回调函数在视图被销毁之前触发,销毁的原因有两种重新渲染和组件卸载。 本文是 React 系列的第二篇 React 新特性讲解及实例(一) 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 什么是 Hooks Hook 是 React 16.8 的新增特性。它可...
摘要:属性是一个组件的外部输入。只会在开发模式下进行属性类型检查,当代码进行生产发布后,为了减少额外的性能开销,类型检查将会被略过。某个类的实例枚举,属性值必须为其中的某一个值。属性为一个数组,且数组中的元素必须符合指定类型。 在第二篇文章 《新型前端开发方式》 中有说到 React 有很爽的一点就是给我们一种创造 HTML 标签的能力,那么今天这篇文章就详细讲解下 React 是如何提供这...
摘要:属性是一个组件的外部输入。只会在开发模式下进行属性类型检查,当代码进行生产发布后,为了减少额外的性能开销,类型检查将会被略过。某个类的实例枚举,属性值必须为其中的某一个值。属性为一个数组,且数组中的元素必须符合指定类型。 在第二篇文章 《新型前端开发方式》 中有说到 React 有很爽的一点就是给我们一种创造 HTML 标签的能力,那么今天这篇文章就详细讲解下 React 是如何提供这...
阅读 1552·2021-11-17 09:33
阅读 1099·2021-11-12 10:36
阅读 2413·2019-08-30 15:54
阅读 2440·2019-08-30 13:14
阅读 2912·2019-08-26 14:05
阅读 3288·2019-08-26 11:32
阅读 3000·2019-08-26 10:09
阅读 2994·2019-08-26 10:09