系列文章目录
第一章:React基础知识(React基本使用、JSX语法、React模块化与组件化)(一)
第二章:React基础知识(组件实例三大核心属性state、props、refs)(二)
第三章:React基础知识(事件处理、受控组件与非受控组件、高阶函数、组件的生命周期)(三)
第四章:React脚手架应用(创建脚手架、代理配置、ajax相关、组件通信)(四)
第五章:react-router5路由相关一(路由相关概念、基本使用、NavLink与NavLink的封装、Switch的使用、严格匹配、路由重定向、路由组件与一般组件的区别)(五)
第六章:react-router5路由相关二(嵌套路由、路由传参、replace、编程式路由导航、withRouter的使用、BrowserRouter与HashRouter的区别)(六)
第七章:React-Router6路由相关一(路由的基本使用、重定向、NavLink·、路由表、嵌套路由)(七)
第八章:React-Router6路由相关二(路由传参、编程式路由导航、路由相关hooks)(八)
第九章:React相关扩展一(setState、lazyLoad、Hooks相关)(九)
第十章:React相关扩展二(Fragment、Content、组件优化、render props、错误边界)(十)
文章目录
- 系列文章目录
- 一、 Fragment
- 二、Context
- 2.1 使用
- 三、useContext
- 四、 组件优化
- 4.1 Component的2个问题
- 4.2 效率高的做法
- 4.3 原因
- 4.4 解决
- 五、render props
- 5.1 如何向组件内部动态传入带内容的结构(标签)?
- 5.2 children props
- 5.3 render props
- 六、错误边界
- 6.1 理解:
- 6.2 特点:
一、 Fragment
(1) 作用
可以不用必须有一个真实的DOM根标签了可以将内部内容当做一个整体,而不产生其他标签,项目运行后会自动删除Fragment标签
(2) 使用
<Fragment><Fragment>
<></>
代码案例:
Demo.jsx
import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
)
}
}
App.js
import React, { Component, Fragment } from 'react'
import Demo from './components/3_hooks'
export default class App extends Component {
render () {
return (
<>
<Demo />
</>
)
}
}
二、Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
2.1 使用
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
注意: 在应用开发中一般不用context, 一般都它的封装react插件
代码案例:
import React, { Component } from 'react'
import './index.css'
import './B.tsx'
//创建Context对象
export const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
//A.tsx
export default class A extends Component {
state = {username:'tom',age:18}
render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{username,age}}>
<B/>
</Provider>
</div>
)
}
}
//B.tsx
import React, { Component } from 'react'
import './index.css'
export default class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}
//c.tsx
import React, { Component } from 'react'
import './index.css'
//创建Context对象
import { MyContext } from './A'
const {Consumer} = MyContext
/*export default class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
export default function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}
运行结果:
三、useContext
与 Context 一样,在 React Hooks 中也提供了更加高级的一种组件中传递值的方式,不再需要一层一层的向下传递,而是可以隔层传递。
1) 创建Context容器对象:
export const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
import {xxxContext } from "./XXX";
const { x } = useContext(xxxContext);
render(){
return (
<div>
{x}
</div>
);
}
代码案例片段:
A.jsx
import React, { Component } from "react";
import "./index.css";
import B from "./B";
//创建Context对象
export const MyContext = React.createContext();
const { Provider } = MyContext;
export default class A extends Component {
state = { username: "tom", age: 18 };
render() {
const { username, age } = this.state;
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
);
}
}
B.jsx
import React from "react";
import "./index.css";
import C from "./C";
const B = () => {
return (
<div className="child">
<h3>我是B组件</h3>
<C />
</div>
);
};
export default B;
C.jsx
import React, { useContext } from "react";
import "./index.css";
import { MyContext } from "./A";
const C = () => {
const { username, age } = useContext(MyContext);
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:
{username},年龄是{age}
</h4>
</div>
);
};
export default C;
运行结果:
四、 组件优化
4.1 Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render()
只当前组件重新render(), 就会自动重新render子组件 ==> 效率低
4.2 效率高的做法
只有当组件的state或props数据发生改变时才重新render()
4.3 原因
Component中的shouldComponentUpdate()总是返回true
4.4 解决
-
办法1:
-
重写shouldComponentUpdate()方法
-
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
代码案例片段:
import React, { Component } from "react"; import "./index.css"; export default class Parent extends Component { state = { carName: "奔驰c36", stus: ["小张", "小李", "小王"] }; addStu = () => { /* const {stus} = this.state stus.unshift('小刘')//不要直接修改state数据, 而是要产生新数据,无法触发页面渲染 this.setState({stus}) */ const { stus } = this.state; this.setState({ stus: ["小刘", ...stus] }); }; changeCar = () => { this.setState({ carName: "迈巴赫" }); // const obj = this.state; // obj.carName = "迈巴赫";//不要直接修改state数据, 而是要产生新数据,无法触发页面渲染 // console.log(obj === this.state);//true // this.setState(obj); }; shouldComponentUpdate(nextProps, nextState) { console.log(this.props, this.state); //目前的props和state console.log(nextProps, nextState); //接下要变化的目标props,目标state return this.state.carName !== nextState.carName; //如果车名没有发生改变,则返回false,阻止渲染,这样虽然添加了一个小李,但是页面也不会渲染 } render() { console.log("Parent---render"); const { carName } = this.state; return ( <div className="parent"> <h3>我是Parent组件</h3> {this.state.stus} <span>我的车名字是:{carName}</span> <br /> <button onClick={this.changeCar}>点我换车</button> <button onClick={this.addStu}>添加一个小刘</button> <Child carName={carName} /> </div> ); } } class Child extends Component { shouldComponentUpdate(nextProps, nextState) { console.log(this.props, this.state); //目前的props和state console.log(nextProps, nextState); //接下要变化的目标props,目标state return !(this.props.carName === nextProps.carName); } render() { console.log("Child---render"); return ( <div className="child"> <h3>我是Child组件</h3> <span>我接到的车是:{this.props.carName}</span> </div> ); } }
运行结果:
-
-
办法2:
-
使用PureComponent
-
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
项目中一般使用PureComponent来优化
代码案例片段:
import React, { PureComponent } from "react"; import "./index.css"; export default class Parent extends PureComponent { state = { carName: "奔驰c36", stus: ["小张", "小李", "小王"] }; addStu = () => { const { stus } = this.state; this.setState({ stus: ["小刘", ...stus] }); }; changeCar = () => { this.setState({ carName: "迈巴赫" }); }; render() { console.log("Parent---render"); const { carName } = this.state; return ( <div className="parent"> <h3>我是Parent组件</h3> {this.state.stus} <span>我的车名字是:{carName}</span> <br /> <button onClick={this.changeCar}>点我换车</button> <button onClick={this.addStu}>添加一个小刘</button> <Child carName={carName} /> </div> ); } } class Child extends PureComponent { render() { console.log("Child---render"); return ( <div className="child"> <h3>我是Child组件</h3> <span>我接到的车是:{this.props.carName}</span> </div> ); } }
运行结果:
注意:
-
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
-
不要直接修改state数据, 而是要产生新数据
-
五、render props
5.1 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
- 使用slot技术, 也就是通过组件标签体传入结构
<AA><BB/></AA>
React中:
- 使用
children props
: 通过组件标签体传入结构- 使用
render props
: 通过组件标签属性传入结构, 一般用render函数属性
5.2 children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
5.3 render props
<A render={(data) => <C data={data}></C>}> </A>
A组件:{this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示{this.props.data}
代码案例片段:
import React, { Component } from "react";
import "./index.css";
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A render={(name) => <B name={name} />} />
</div>
);
}
}
class A extends Component {
state = { name: "tom" };
render() {
console.log("A--render", this.props);
const { name } = this.state;
return (
<div className="a">
<h3>我是A组件</h3>
{this.props.render(name)}
</div>
);
}
}
class B extends Component {
render() {
console.log("B--render", this.props);
return (
<div className="b">
<h3>我是B组件,{this.props.name}</h3>
</div>
);
}
}
运行结果:
六、错误边界
6.1 理解:
错误边界: 用来捕获后代组件错误,渲染出备用页面
6.2 特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
代码案例片段:
Children.jsx
import React, { Component } from "react";
export default class Child extends Component {
state = {
// users:[
// {id:'001',name:'tom',age:18},
// {id:'002',name:'jack',age:19},
// {id:'003',name:'peiqi',age:20},
// ]
users: "abc",
};
render() {
return (
<div>
<h2>我是Child组件</h2>
{this.state.users.map((userObj) => {
return (
<h4 key={userObj.id}>
{userObj.name}----{userObj.age}
</h4>
);
})}
</div>
);
}
}
Parent.jsx
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}
运行结果: