8.4 React高级 🎉
8.4 React高级 🎉
Context应用 💎
React组件中数据是通过 props 属性自上而下(由父及子)进行传递的,Context也可以传递数据,props 是父传子,但是假如子组件还有子组件的话,就构成了(父亲-儿子-孙子)的关系了,如果把父亲的数据传递给孙子,使用Context无疑是最好的选择,它可以跨越组件的层级关系。
首先看一下props 传递的方式
import React, { Component } from 'react'
import Child from "./child"
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
user: {
name: 'lcl',
age: 30
}
}
}
render() {
return (
<div>
<p>父亲</p>
<Child value={this.state.user} />
</div>
)
}
}
export default Parent
import React from 'react'
import Sunzi from "./sunzi"
class Child extends React.Component {
render() {
return (
<div>
<p>儿子</p>
<Sunzi value={this.props.value} />
</div>
)
}
}
export default Child
import React from 'react'
class Sunzi extends React.Component {
render() {
return (
<div>
<p>孙子</p>
<p>{this.props.value.name}</p>
</div>
)
}
}
export default Sunzi
通过以上操作,数据就从父组件借助儿子组件传递到了孙子组件中
再看用Context的传递方式
Context
提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。如果我们要在组件树中去共享某些数据,并且要避免通过中间元素传递 props,则可以使用Context来实现。
- 创建Context对象
React.createContext(defaultValue)
- 使用
Context
的Provider
组件
Contenxt的Provider组件用来提供其它组件要共享的数据。
设置value
属性来设置要共享的数据。
我们把要使用共享数据的组件称为消费组件
。
<MyContext.Provider value={/* 某个值 */}>
<Home />
</MyContext.Provider>
这样<父组件 />
组件下的所有子组件都能拿到value的值
- 添加
contextType
属性
MyClass.contextType = MyContext;
提示
挂载在 class 上的 contextType
属性会被重赋值为一个由 React.createContext()
创建的 Context 对象。这能让你使用 this.context
来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
使用方式
首先,创建Context对象,把他单独整理成一个插件
// /src/utils/MyContext.jsx
import React from 'react'
export default React.createContext("随便");
下面就能传递数据了,任意子组件均可获取
import React, { Component } from 'react'
import Child from "./child"
import MyContext from './utils/MyContext';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
user: {
name: 'lcl',
age: 30
}
}
}
render() {
return (
<div>
<p>父亲</p>
<MyContext.Provider value={this.state.user}>
<Child />
</MyContext.Provider>
</div>
)
}
}
export default Parent
import React from 'react'
import Sunzi from "./sunzi"
import MyContext from './utils/MyContext';
class Child extends React.Component {
render() {
const data = this.context
console.log(data.name);
return (
<div>
<p>儿子</p>
<p>{data.age}</p>
<Sunzi />
</div>
)
}
}
export default Child
Child.contextType = MyContext
import React from 'react'
import MyContext from './utils/MyContext';
class Sunzi extends React.Component {
render() {
const data = this.context
console.log(data.name);
return (
<div>
<p>孙子</p>
<p>{data.name}</p>
</div>
)
}
}
export default Sunzi
Sunzi.contextType = MyContext
注意
函数组件也是可以订阅Context的,这里我只说类组件的使用方式,后面我们为你学习了ReactHook后使用更为简单的方式进行传递
Fragments 💎
react
中的Fragments
,就好比vue
中的template
或者小程序中的block
,只会起到包裹的作用,并不会真正的渲染dom结构
import React from 'react'
const Parent = () => {
const dom1 = (
<div>
<p>1</p>
<p>2</p>
</div>
)
const dom2 = (
<React.Fragment>
<p>3</p>
<p>4</p>
</React.Fragment>
)
return (
<div>
{dom1}
{dom2}
</div>
)
}
export default Parent
最后,打开控制台,查看页面生成的dom结构
Fragments短语法
使用<></>
代替<React.Fragment></React.Fragment>
const dom2 = (
<>
<p>3</p>
<p>4</p>
</>
)
因为Fragments最终不会被渲染为DOM,所以不要在Framents上面绑定事件或者设置一些其它属性,目前只支持设置key属性。
错误边界 💎
通常某一个组件中发生的错误会导致整个应用崩溃,页面一片空白。
仅仅一个小组件的报错,让整个项目应用都无法显示,这就身份不可合理了,如果我们想让未出现错误的组件还能继续渲染,则可以使用错误边界
。错误边界
是一种 React 组件,可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它可以渲染出备用 UI,在渲染期间、生命周期方法和整个子组件树的构造函数中捕获错误。
第一步,定义一个错误边界的组件:
class 组件中定义了 static getDerivedStateFromError()
或 componentDidCatch()
这两个生命周期方法中的任意一个(或两个)时,那么这个组件就变成一个错误边界的组件。
// /src/utils/error.jsx
import React from 'react';
class Error extends React.Component {
constructor(props) {
super(props)
this.state = {
error: null,
errorInfo: null
}
}
componentDidCatch(error, errorInfo) {
// 捕获到子组件树当中发生的错误时调用
this.setState({
error: error,
errorInfo: errorInfo
})
}
render() {
return (
<div>
{
this.state.errorInfo
?
<div>
<p>组件发生错误</p>
<p>错误信息如下</p>
<p> {this.state.error && this.state.error.toString()}</p>
<p> {this.state.errorInfo.componentStack}</p>
</div>
:
this.props.children
}
</div>
)
}
}
export default Error
import React from 'react'
import Error from './utils/error'
import Child from './child'
import Sunzi from './sunzi'
const Parent = () => {
return (
<div>
<p>我是父亲,如果我的后代有错误,我依旧正常显示</p>
<Error>
<Child />
</Error>
<Error>
<Sunzi />
</Error>
</div>
)
}
export default Parent
import React from 'react'
const Child = () => {
return (
<div>
<p>我没错误</p>
</div>
)
}
export default Child
import React from 'react'
const Sunzi = () => {
return (
<div>
<p>我有错误</p>
<p>{data.name}</p>
</div>
)
}
export default Sunzi
虽然子组件有错误,但不影响其他组件的显示
错误边界无法捕获以下场景中产生的错误:
- 事件处理
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调函数) - 它自身抛出来的错误(并非它的子组件)
解决办法用:
try catch
window.onerror
window.onerror可以捕捉语法错误,也可以捕捉运行时错误,只要在当前window执行的Js脚本出错都会捕捉到。
window.onerror=function(err){ console.log(err) }
Refs操作DOM 💎
Refs 提供了一种方式,允许我们访问DOM 元素。
<div ref={ref}></div>
创建 Refs
使用
React.createRef()
创建的。通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
constructor(props) { super(props); this.myRef = React.createRef(); }
使用Refs
给对应的React 元素设置
ref
属性,则相当于使用ref
去存储 DOM 节点的引用。render() { return <input ref={this.myRef} />; }
访问Refs
当 ref 被传递给
render
中的元素时,对该节点的引用可以在 ref 的current
属性中被访问。componentDidMount(){ console.log(this.myRef.current); }
提示
React 会在组件挂载时给 current
属性传入 DOM 元素,并在组件卸载时传入 null
值。ref
会在 componentDidMount
或 componentDidUpdate
生命周期钩子触发前更新
Refs & 类组件 👻
使用Refs的时候,当 ref 属性用于自定义 class 组件时,ref 对象接收组件的实例作为其 current 属性。
import React from 'react'
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef();
}
componentDidMount() {
const node = this.myRef.current;
console.log(node);
}
render() {
return (
<div>
<p ref={this.myRef}>父亲</p>
</div>
)
}
}
export default Parent
ref不仅可以直接定义在元素上,也可以定义在组件上
使用Refs的时候,当 ref
属性用于自定义 class 组件时,ref
对象接收组件的实例作为其 current
属性。
import React from 'react'
import Child from './child'
// import Sunzi from './sunzi'
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef();
this.comRef = React.createRef();
}
componentDidMount() {
const node = this.myRef.current;
const comenode = this.comRef.current;
console.log(node, comenode);
this.comRef.current.onIndo() //访问并调用到了类组件的方法
}
render() {
return (
<div>
<p ref={this.myRef}>父亲</p>
<Child ref={this.comRef} />
</div>
)
}
}
export default Parent
import React from 'react'
class Child extends React.Component {
onIndo = () => {
console.log(1);
}
render() {
return (
<div>
<p>我没错误</p>
</div >
)
}
}
export default Child
回调 Refs & 类组件 👻
React 也支持另一种设置 refs 的方式,不同于传递 React.createRef() 创建的Refs,我们可以给ref属性传递一个函数。在这个函数中通过参数来获取 React 组件实例或 HTML DOM 元素。
import React from 'react'
import Child from './child'
class Parent extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
}
inputDom
setInput = (ele) => {
console.log(ele);
this.inputDom = ele
}
setcomRef = (ele) => {
console.log(ele);
this.inputDom = ele
ele.onIndo() //访问并调用到了类组件的方法
}
render() {
return (
<div>
<input ref={this.setInput} />
<Child ref={this.setcomRef} />
</div>
)
}
}
export default Parent
import React from 'react'
class Child extends React.Component {
onIndo = () => {
console.log(1);
}
render() {
return (
<div>
<p>21212</p>
</div >
)
}
}
export default Child
高阶组件(HOC) 💎
高阶组件(HOC)
是以组件为参数,返回值为新组件的函数。
可以把高阶组件
看作是组件的加工厂
,接收旧组件返回包装后的新组件。
什么情况下使用高阶组件
react如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复
比如:我想在不同的组件中,分别获取用户信息,创建一个高阶组件
// /src/utils/hoc.tsx
import React from 'react'
const Hoc = (WrappedComponent) => {
return class extends React.Component {
constructor(props) {
super(props)
// 初始化state的userInfo属性
this.state = {
title:"标题",
userInfo: {
name: "lcl"
}
}
}
componentDidMount() {
// 获取用户信息,并且存储到state当中
let userInfo = {
name: "lcl"
}
this.setState({
userInfo: userInfo
})
}
render() {
return (
<WrappedComponent userInfo={this.state.userInfo} {...this.props} />
)
}
}
}
export default Hoc
import React from 'react'
import Child from './child'
import Hoc from "./utils/hoc"
const Parent = (props) => {
return (
<div>
<p>父组件自身要获取用户信息</p>
<p>你好:{props.userInfo.name}</p>
<Child name='11111111111111' />
</div>
)
}
export default Hoc(Parent)
import React from 'react'
import Hoc from "./utils/hoc"
const Child = (props) => {
return (
<div>
<p>这个子组件要获取用户信息</p>
<p>{props.userInfo.name}</p>
<p>接收多余的props参数</p>
<p>{props.name}</p>
</div >
)
}
export default Hoc(Child)
参数组件(被包装组件)除了接收需要从高阶组件返回的新组件(容器组件)获得的数据之外,还需要接收来自容器组件的所有 props,如代码中高亮的部分。