React学习笔记

265 阅读16分钟

1. 虚拟DOM

(1) 虚拟DOM说明
- 本质是Object类型的对象
- 虚拟DOM比较‘轻’(包含的属性较少),真实DOM比较‘重’。因为虚拟DOM是React内部在用,无需真实DOM上那么多属性。
- 虚拟DOM最终会被react转化为真实DOM呈现在页面中。
(2) 构建虚拟DOM的方法
- 使用jsx语法直接写虚拟dom
- React.createElement(标签名,标签属性,标签内容)方法创建
  React.createElement('h1', {id: 'title'}, 'hello React')
(3) 虚拟DOM中的key的作用
当状态中的数据发生变化时,react会根据新数据生成【新的虚拟DOM】,随后进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则:  
a. 新虚拟DOM中找到了与旧虚拟DOM中相同的key  
    若虚拟DOM中内容没变,直接使用之前的真实DOM  
    若虚拟DOM中内容变了,则生成新的真实DOM替换掉页面中之前的真实DOM  
b. 新虚拟DOM中没找到与旧虚拟DOM中相同的key  
    根据数据创建新的真实DOM放到页面中

3. jsx语法规则

- 定义虚拟DOM时不用加引号
- 标签中混入js表达式时要用{},注释也需要用大括号包裹{}
- 样式类名指定使用className非class,
- 内联样式要用style={{key:value}}的形式写
- 虚拟DOM只有一个根标签 
- 标签必须闭合;标签首字母:
 (1)若小写字母开头,则将该标签转换为html中的同名标签,若html中无该标签对应的同名元素则报错
 (2)若大写字母开头,React则去渲染对应的组件,若组件没有定义则报错
- 标签体内容是一个特殊的标签属性,可以使用属性形式书写(key:children,value:xxx),并且在子组件中可以通过this.props.children获取到父组件的标签体
// react语法中style不用写单位‘px’,或者加单位用字符串包裹'30px';lineHeight只能加px;且style后面内容无论是否为变量均需用{}包裹,如style={{display:'none'}}
const myStyle = {
  background: blue,
  fontSize: 30
}
const vDom = <span className={myclass} style={myStyle}>{mycontent}</span>
ReactDOM.render(
  <div>
    {/*我是注释*/}
    {vDom}
  </div>
)

// 标签体内容的两种写法示例
// 父组件
import Child1 from './component/child1'
import Child2 from './component/child2'

<Child1>我是标签体内容</Child1>
<Child2 children="我是标签体内容" />

// Child1子组件
<h1>{this.props.children}</h1>
// Child2子组件
<h1 children={this.props.children} />

/* 根据数组循环dom元素 */
const arr = [
  { name: 'zhangsan', age: 18 },
  { name: 'lisi', age: 12 }
]
// map返回项只有一个返回值,可以省略return
const vDom = (
  <ul>
     {
        arr.map((item, index) =>
          <li key={index}>姓名:{item.name} 年龄:{item.age}</li>
        )
     }
  </ul>
)

4. 组件化(函数式组件、类式组件)

执行了ReactDOM.render(<MyComponent...)之后发生了什么?
- react解析组件标签,找到了MyComponent组件,发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM呈现在页面中
- react解析组件标签,找到了MyComponent组件,发现组件是使用类定义的,随后New出来该类的实例,并通过该实例调用到原型上的render方法,将render返回的虚拟DOM转为真实DOM呈现在页面中

函数式组件:没有this,无法使用state、refs,后期可以通过hooks访问

function Demo(props) {
    // 函数式组件可以使用react组件三大属性中的props属性
    const {name, age} = props
    return (
        <h1>我是函数式组件</h1>
    )
}
ReactDOM.render(<Demo name="tom" age={18} />, document.getElmentById('root'))

类式组件

render是放在组件的原型对象上,供组件实例使用
(1)组件props传值
字符串等简单类型数据直接传,对象、数组等用{}包裹,组件中获取到传入值为简单数据类型的数组,自动将数组项提取成字符串加空格拼接展示  
import React from 'react';
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
    // 给标签属性设置默认值
    static defaultProps = {
    	name: 'a',
        age: 18
    }
    // 对标签(<MyComponent />)属性进行类型、必要性的限制;字段格式string、number、array、object、bool、func等
    // 依赖props-types.js文件
    static propTypes = {
    	name: PropTypes.string.isRequired, // 规定字段为字符串且必传
        age: Proptypes.oneOfType([PropTypes.string, PropTypes.number]) // 规定字段为字符串或数字
    }
    render() {
        return (
            <div>
                <p>name:{this.props.name}</p>
                <p>name:{this.props.obj.name} age:{this.props.obj.age}</p>
                <p>arr:{this.props.arr}</p>
            </div>
        )
    }
}
// 方法1:
// 如下props.arr直接展示为'haha lala',传入值为数字类型则用{}包裹
ReactDom.render(
	<MyComponent name="qing" age={8} obj={{name:'qing',age:18}} arr={['haha','lala']} />, 
 	document.getElementById('app')
)
// 方法2:
const obj = {name: 'qing', age: 8, obj: {name: 'qing', age: 18}, arr: ['haha', 'lala']}
ReactDom.render(
	<MyComponent { ...obj } />, 
 	document.getElementById('app')
)
(2)onXxxx属性指定事件处理函数
React使用的是自定义(合成)事件而非DOM的原生事件---为了更好的兼容性
React中的事件是通过事件委托的方式处理的,委托给组件最外层元素---为了高效
(3)组件ref的使用
- 创建容器存储ref:使用React.createRef()方法创建容器,绑定的dom元素存在容器的current中
- 回调函数式refref={currentNode => this.inputNode = currentNode }
- 类绑定式ref,类似回调函数式
class RefExample extends React.Component {
    constrctor(props) {
        // 构造器中是否接收并传递props给super,取决于是否希望在构造器中通过this访问props
    	super(props)
    }
    
    // React.createRef调用后返回一个容器,可以存储被ref标识的节点,通过容器.current访问存储的节点
    inputRef = React.createRef()
    
    setInputRef = (c) => {
        this.input3 = c
    }
    
    render() {
    	return (
            <div>
                {/* 创建容器存储ref */}
            	<input ref={this.inputRef} type="text" />
                <button onClick={() => this.focusInput()}>绑定事件方法一</button>   
                
                {/* 回调函数式ref;与class绑定式的区别是,内联运行完会释放,视图更新时会再次调用且调用两次,第一次接收到的参数为null(清空) */}
                <input ref={currentNode => this.input2 = currentNode} type="text" />
                <button onClick={this.inputFocus}>绑定事件方法二</button>
                
                {/* class绑定式ref */}
                <input ref={this.setInputRef} type="text" />
            </div>
        )
    }
    // 写在对象原型中的方法
    focusInput() {
    	// dom元素在current对象中
    	this.inputRef.current.focus()
    }
    // 绑定事件第二种写法,给实例对象添加属性方法
    inputFocus = () => {
        this.input2.focus()
    }
}
4⃣️组件生命周期(新版:17.0.1起)
卸载组件                                                               
ReactDOM.unmountComponentAtNode(卸载的dom根节点)

初始化阶段:由ReactDOM.render()触发                
constructor  
componentWillMount(旧) --> UNSAFE_componentWillMount(新):
    组件将要挂载 
getDerivedStateFromProps(新)
    组件的静态方法,接收props,state参数,必须指定返回值(限制返回组件状态对象或null),state的值取决于返回的状态对象值无法通过修改状态值变更
render: 
    初始化渲染调用一次                              
componentDidMount
    组件挂载完毕调用一次

更新阶段:由组件内部this.setState()或父组件重新render触发
componentWillReceiveProps(旧)-->UNSAFE_componentWillReceiveProps(新)
    组件将要接收新的props,可接收到props参数;(父组件重新render后调用,第一次收到传值不调用)      
shouldComponentUpdate: 
    setState更新状态数据后,控制组件更新的‘阀门’,可以接收nextProps、nextState两个参数,返回布尔值。 注:调用组件实例的forceUpdate方法,在不更新状态里面数据的情况下强制组件更新,可跳过‘阀门’钩子  
componentWillUpdate(旧) --> UNSAFE_componentWillUpdate(新)
    组件将要更新 
render
    每次状态更新后调用  
getSnapshotBeforeUpdate(新):  
    在页面更新前获取快照;必须指定返回值,返回值作为第三个参数传递给下一个钩子
componentDidUpdate 
    组件更新完成,收到两(三:新版)个参数preProps,preState,snapshotValue

卸载阶段:由ReactDOM.unmountComponentAtNode(container)触发  
componentWillUnmount
    组件将要被卸载      
componentDidCatch
    错误处理:一般用于统计错误,反馈给服务器,通知开发人员处理bug

5. Dom

(1)input框双向绑定

class MyInput extends React.Component {
    constructor () {
    	// 初始化状态,类似Vue中的data
    	this.state = {
          userName: '',
          userPassword: ''
        }
    }
    render() {
    	const { userName, userPassword } = this.state
        return (
            <form onSubmit={() => this.submit()}>
            	<input type="text" value={userName} onChange={(e) => this.userChange(e)}/>
                <input type="password" value={userPassword} onChange={(e) => this.pwdChange(e)}/>
            </form>
        )
    }
    submit() {}
    userChange(e) {
    	// 使用setState修改数据模型中的数据,传入对象是与原来的state做合并更新,不是覆盖
    	this.setState({ userName: e.target.value })
    }
}

6.Redux

(1)简介

三方库redux,类似Vue中的Vuex,集中式管理react应用中多个组件共享的状态;action、store、reducer三个核心概念 
- action:
  同步action为对象,包含type和data两个属性({type: 'ADD_COUNT', data: 2}); 
  异步action为函数,依赖redux-thunk库,store读到action为函数时,先调用函数执行一下,而非直接交给reducer处理;异步action中一般会调用同步action,在相应的时间处理数据    
- reducer:初始化、加工状态,加工时根据旧的state和action,产生新的state的纯函数,combineReducers用来合并reducers;  
           *注:redux中的reducer必须是一个纯函数*   
- store:将state、action、reducer联系在一起的对象   
        ----------------得到store对象----------------------      
        import {createStore, combineReducers} from 'redux'  
        import countReducer from './reducers/count'  
        import personReducer from './reducers/person'  
        const allReducers = combineReducers(
        {count: countReducer, person: personReducer})    
        const store = createStore(allReducers)   
        --------------支持异步action的store对象-----------       
        import {createStore, applyMiddleware} from 'redux'  
        import reducer from './reducers'  
        import thunk from 'redux-thunk'  
        const store = createStore(reducer, applyMiddleware(thunk))   
        ---------------store对象的功能---------------------     
        getState():得到state  
        dispatch(action):分发action,触发reducer调用,产生新的state  
        subscribe(listener):注册监听,产生新的state时候自动调用;通常在index.js文件中使用,监听到变化时重新render整个app;
        store.subscribe(ReactDOM.render(<App/>,document.getElementById('root')))

(2)redux文件结构

 src
  │   components
  │   static
  |   App.js
  |   index.js
  │   ...
  └───store
      │   index.js
      |   constant.js 放actions的type变量
      └───actionCreators
          │ count.js
          │ person.js
      └───reducers
          │ count.js
          │ person.js

(3)使用官方react-redux对状态管理进行优化

  src
  │   components 放一般组件
  │   container 放容器组件,内部定义对应UI组件
  │   static
  |   App.js
  |   index.js
  │   ...
  └───store
      │   index.js
      |   constant.js
      └───actionCreators
          │ count.js
          │ person.js 
      └───reducers
          │ count.js
          │ person.js
  • 将组件区分为容器组件与UI组件,容器组件负责与store做交互,UI组件中通过属性调用的方式实现交互
  • 在容器组件中引入react-redux中的connect方法来连接UI组件与redux
/* 容器组件示例 */
import React, {Component} from 'react'
import {connect} from 'react-redux'  
import {xxxxAction} from '../../store/actionCreators'  // 引入createActions  
// store不通过此处引入,只能从外层传入

// UI组件内容
class Count extends Component {
    render() {
        // 可以通过this.props.xxxx(key)读取和操作状态
        return ...
    }
}

// 默认暴露出容器组件
export default connect(
  /* 
      mapStateToProps映射状态
      1. 用于向ui组件传递状态
      2. 函数返回一个对象
      3. 返回对象中的key就作为传递给ui组件props的key,value作为传递给ui组件props的value
  */
  state => ({key: value}),
  /* 
      mapStateToProps映射操作状态的方法
      1. 用于向ui组件传递操作状态的方法
      2. mapStateToProps可以是一个函数也可以简写成一个对象,为对象时底层会自动调用dispatch方法
      3. 返回对象中的key就作为传递给ui组件props的key,value作为传递给ui组件props的value
  */
  { key: xxxxAction }
)(Count)

/* 引入容器组件示例 */
import React, {Component} from 'react'
import Count from './container/Count'
import store from './store'

export default class App extends Component {
    render() {
        <div>
            <Count store={store} />
        </div>
    }
}

/* index.js */
// 省略部分引入项
...
import store from './store'
import {Provider} from 'react-redux'

ReactDOM.render(
  {/* App中所有容器组件都能收到store,不用在组件中一个个传 */}
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
* 结合react-redux使用状态管理工具的优点  
  - 无需在index.js中自己通过store.subscribe()检测redux中状态的改变,connect内部自动完成这个工作  
  - 无需自己给容器组件中传入store,给App包裹一个\<Provider store={store}>即可

7.react-router(web端使用react-router-dom库)

- 项目路由用路由器BrowserRouter/HashRouter管理,整个项目统一使用一个路由器,通常包裹在入口index.js文件中  
- BrowserRouter:Bom中history的应用  
- HashRouter:url中的哈希运用,刷新后会导致路由state参数的丢失,可以解决一些路径错误相关的问题
- 路由链接Link、NavLink负责切换组件,指定跳转的路由地址  
- 路由Route负责注册路由,指定路由组件  
- 路由组件可以收到路由器传递的三个props属性history、location、match
  history ---> go、goBack、goForward、push、replace  
  location ---> hash、pathname、search、state  
  match ---> params、path、url
- 嵌套路由:注册子路由时要写上父路由的path值
- 路由的匹配值按照注册路由的顺序进行
- withRouter:加工一般组件,让一般组件中具备路由组件特有的属性api
// 入口index.js
{/* 路由器 负责包裹路由标签 */}
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDom.render(<BrowserRouter><App/></BrowserRouter>, document.getElementById('root'))

// 组件文件
// 引入路由链接组件、路由、重定向组件等
import { Switch, Link, NavLink, Route, Redirect } from 'react-router-dom'
// 路由组件通常放在pages文件夹中,一般组件放在components文件夹中
import About from './pages/About'
import Home from './pages/Home'

// render中内容:
/* 导航部分: */
/* 编写路由链接 */
<Link className="xxx" to="/about">About</Link>
<Link className="xxx" to="/home">Home</Link>
/* 编写路由链接,可使用activeClassName指定高亮时的样式名,默认active */
/* exact属性开启精准匹配,默认模糊匹配,即输入的路径包含要匹配的路径且顺序一致则匹配成功;
exact不可随意开启,可能导致无法匹配二级路由 */
<NavLink activeClassName="xxx" className="xxx" to="/about/a/b" exact>About</NavLink>
/* 标签体可以用属性children的形式传 */
<NavLink activeClassName="xxx" className="xxx" to="/home/a/b" children="Home" />

/* 根据路由展示组件部分: */
/* 默认一直匹配到结束,包裹switch优化路由匹配效率,匹配到一个后不再继续匹配 */
<Switch>
    /* 注册路由 */
    /* 路由链接已开启精准匹配,此处无法匹配 */
    <Route path="/about" component={About} />
    /* 路由链接为默认模糊匹配,此处可匹配 */
    <Route path="/home" component={Home} />
    /* 以上都无法匹配则走Redirect组件指定的默认路由 */
    <Redirect to="/about"/>
<Switch/>
给路由组件传值的三种方式  
- 通过params给路由组件传值:路由链接中传入参数,注册路由时声明接收  
- 通过search给路由组件传值:路由链接中传入参数,注册路由无需声明接收  
- 通过state给路由组件传值:路由链接中传入参数,注册路由无需声明接收;地址栏无信
息,state信息保存在history对象中,刷新时依然保留住参数,清理history缓存后参数
无法保留(HashRouter的方式刷新后会丢失state参数)
/* 方法一 */ 
/* 向路由组件传递params参数 */
<Link to={`/home/message/detail/${参数1变量}/${参数2变量}`}>Message</Link>
/* 声明接收params参数,组件中通过this.props.match.params拿到传递的参数对象 */
<Route path="/home/message/detail/:id/:title" component={Detail}/>

/* 方法二 */ 
/* 向路由组件传递search参数 */
<Link to={`/home/message/detail?id=${参数1变量}&title=${参数2变量}`}>Message</Link>
/* 组件中通过this.props.location.search拿到传递的urlEncoded编码形式参数key=value&key=value,可借助querystring库转译成对象形式 */
<Route path="/home/message/detail" component={Detail}/>

/* 方法三 */ 
/* 向路由组件传递state参数 */
<Link to={{pathname:'/home/message/detail', state: {id:参数1变量,title:参数2变量}}}>Message</Link>
/* 组件中通过this.props.location.state拿到传递的参数对象 */
<Route path="/home/message/detail" component={Detail}/>
- 路由包括push与replace模式:默认push模式  
- 编程式路由导航
// 一般路由导航
<Link to="/home/message/detail">Message</Link>
/* 开启replace模式,匹配此路由时直接替换原有路由不形成历史记录 */
<Route replace path="/home/message/detail" component={Detail}/>

// 编程式路由导航
render() {
    return (
        <button onClick={() => {this.replaceShow(id参数,title参数)}}>replace跳转</button>
        {/* 用params传参方式时在path中声明接收*/}
        <Route path="/home/message/detail" component={Detail}/>
    )
}
replaceShow = (id,title) => {
    // push模式使用this.props.history.push方法
    // params传参
    this.props.history.replace(`/home/message/detail/${id}/${title}`)
    // query传参
    this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
    // state传参
    this.props.history.replace('/home/message/detail', {id,title})
}

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom'

class Header extends Component {
    render() {
        const {history, params, match} = this.props
    }
}

// withRouter加工后的Header组件中可以访问到路由组件的三大属性
export default withRouter(Header)

React浏览器调试插件

React Developer ToolsRedux DevTools
Redux DevTools依赖redux-devtools-extension插件,在store的代码中如下配置:
此处省略部分引用
...
import {composeWithDevTools} from 'redux-devtools-extension'
export default createStore(reducers, composeWithDevTools(ApplyMiddleware(thunk)))

补充知识点

css模块化引入:
- 文件名中加入module(如index.module.css),组件中以import Xxx from 'xxxx'形式引入,模板中样式可以用Xxx.xxx形式访问定义的类名等

setState:是同步方法,但是调用之后引起react后续更新状态的动作是异步的  
- 对象式:setState(stateChange, [callback])  不依赖于原状态时推荐使用
         callback是在状态更新完毕、界面也更新后(render调用后)才被调用
- 函数式:setState(updater, [callback]) 依赖原状态时推荐使用
         updater为返回stateChange对象的函数,可以接收到state和props参数
         
组件间通信方式整理
- props(childrenProps、renderProps):父子组件
- 集中式状态管理工具(redux):兄弟组件、祖孙组件(跨级组件)
- context(生产者--消费者模式):祖孙组件(跨级组件)
- 消息订阅与发布机制,依赖库pubsub-js:兄弟组件、祖孙组件(跨级组件)
  ```
    import Pubsub from 'pubsub-js'
    // 订阅消息
    Pubsub.subscribe('订阅的消息名', (msg, data) => {
        // 接收到消息后的回调函数,data为接收到的消息内容,msg为消息名
    )
    // 发布消息
    Pubsub.public('发布的消息名', 发布的消息内容)
    // 取消订阅
    Pubsub.unsubscribe('取消订阅的消息名')
  ```

Context:一种组件间通信方式,常用于祖组件与后代组件通信;在react应用中一般不用context,一般用来封装react插件
 ```
     const MyContext = React.createContext()
     // 祖组件
     class A extends Component {
       state = {username: 'tom', age: 18}
       render() {
         return (
           <div>
             {/* B组件及其所有后代组件都能通过this.context拿到传递的值,但是需要声明接收 */}
             <MyContext.Provider value={this.state.username}>
               <B/>
             </MyContext.Provider>
           </div>
         )
       }
     }
     class B extends Component {
       // 声明接收context方法1:只适用于类式组件,声明后this.context就获取到传来的value值
       static contextType = MyContext
       render() {
         return (
           <div>
             <h1>我是B组件<h1>
             <div>我接收到A组件人名为:{this.context}</div>
           </div>
         )
       }
     }
     class C extends Component {
       render() {
         return (
           <div>
             <h1>我是C组件<h1>
             <div>我接收到A组件人名为:
               {/* 声明接收context方法2:类式组件、函数式组件通用*/}
               <MyContext.Consumer>
                 {
                   value => {
                     // value就是传来的值
                     return <span>{value}</span>
                   }
                 }
               </MyContext.Consumer>
             </div>
            </div>
          )
        }
     }
 ```
         
lazyload:路由组件懒加载,依赖react中的lazySuspense  
- 引入懒加载的组件:const Home = lazy(() => {import('./xx')})
- 注册路由时用Suspense包裹,指定等待加载时展示的内容,fallback可以传虚拟dom也可以传组件,传组件时不可以是懒加载组件。
  ```js
     <Suspense fallback={<h1>Loading</h1>}>
         <Route path="/home" component={Home}/>
     </Suspense>
  ```
  
hooks: ^16.8.0版本,在函数式组件中使用state及其他react特性
- stateHook:state管理功能,通过React.useState(initState值)实现
- effectHook:生命周期功能,通过React.useEffect()实现
- refHook:类似React.createRef()功能,通过React.useRef()实现
  ```
     function Demo() {
         // 第一次初始化指定的值内部做缓存,第二次调用不会重新初始化
         const [count, setCount] = React.useState(1)
         const [name, setName] = React.useState('tom')
         const myRef = React.useRef()
         
         // 第二个参数为一个数组[],为确定监测某个数据变化时再调用钩子
         React.useEffect(() => {
             // 根据第二个参数传值的不同,相当于componentDidMount及componentDidUpdate钩子的作用
             // 传空数组就都不监听,类似componentDidMount作用,只执行一次
             // 不传及传部分数据就相当于componentDidUpdate,不传就默认监听所有state数据
             return () => {
                 // 我是类似componentWillUnmount的钩子
             }
         },[count])
         
         function add() {
             // 修改状态的两种方法
             setCount(count + 1)
             // setCount(count => count + 1)
         }
         function changeName() {
             setName('jerry')
         }
         return (
             <div>
                 <h2>当前数字为:{count}</h2>
                 <button onClick={add}>点我加1</button>
                 <h2>我的名字是:{name}</h2>
                 <button onClick={changeName}>点我改名</button>
                 <input ref={myRef} type="text" />
             </div>
         )
     }
  ```

 Fragment:类似Vue中的template元素,Fragment标签可以拥有一个key属性
 ```
     import {Fragment} from 'react'
     class Demo extends Component {
         render() {
             return (
                 <Fragment>
                     <h2>...</h2>
                     <input type="text"/>
                 </Fragment>
             )
         }
     }
 ```
 
 Component的两个问题:
 - 只要执行setState(),即使不改变状态数据,组件也会重新render()
 - 只要当前组件重新render(),就会重新render所有的子组件,即使子组件没有用到当前组件的任何数据
   解决方案1: 利用shouldComponentUpdate钩子,对props/state是否变化进行判断,确定是否需要重新render组件
   解决方案2: 使用react中的PureComponent组件替代Component组件创建类式组件,PureComponent重写了shouldComponentUpdate,只有props或state发生变化时才返回true;
             注意:PureComponent只对props及state进行了浅比较,如果只是改了数据对象内部的数据,对象指针没有变化,返回false。
             
 renderProps:组件插槽,类似Vue中的slot
 ```
     export default class Parent extends Component {
         render() {
             return (
                 <div>
                     <h1>我是Parent组件</h1>
                     <A render={name => <B name={name} />} />
                 </div>
             )
         }
     }
     
     class A extends Component {
         state = { name: 'tom' }
         render() {
             return (
                 <div>
                     <h1>我是A组件</h1>
                     {this.props.render(this.state.name)}
                 </div>
             )
         }
     }
     
     class B extends Component {
         render() {
             return (
                 <div>
                     <h1>我是B组件</h1>
                     <div>我收到的名字是:{this.props.name}</div>
                 </div>
             )
         }
     }
 ```
 
 ErrorBoundary:错误边界,只适用于生产环境
 - 用于捕获后代组件错误,渲染出备用页面
 - 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
 ```
     export default class Parent extends Component {
         state = {childHasError: ''} // 标识子组件是否发生错误
         // 当Parent的子组件发生报错时调用,并携带错误信息
         static getDerivedStateFromError(error) {
             // state的数据值取决于返回的状态对象,无法修改
             return {childHasError: error}
         }
         
         render() {
             return (
                 <Fragment>
                     {this.state.childHasError ? <h1>当前网络不稳定,请稍后再试</h1> : <Child/>}
                 </Fragment>
             )
         }
     }
 ```