React基础v2

父子组件通信

  1. props -父组件向子组件传递数据

    • 父组件
      import Header from './Header'  // 引入子组件Header
      this.state = { msg:'123' }
      run= () => {
         // ...
      }
      render() {
          return(<div><Header msg={this.state.msg} /><div>)
      }
      
    • 子组件Header.js
      constructor(props) {  // props 是一个对象,用于接收父组件的数据
          super(props)
      }
      render() {
          return(<div>
              <span>{this.props.msg}<span>
              <button onClick={this.props.run}>Run Test</button>
          <div>)
      }
      
  2. Context API - 跨层级通信,祖代组件向任意层级的后代组件传递
    ps:Vue 中的 provide/inject 来源于Context
    Context模式下有两个角色:Provider - 外层提供数据的组件,Consumer - 内存获取数据的组件。

    • 创建 Context 上下文,放在一个文件中,方便引用
      // storecontext.js
      
      import React from 'react'
      // Context 可以创建多个!
      const StoreContext = React.createContext(/*可以设置默认值*/)
      export default StoreContext
      
      React 渲染一个订阅 Context 对象的组件时,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值;只有当组件所在的树中没有匹配到 Provider 时,设置的默认值才会生效。这有助于在不使用 Provider 包装组件的情况下,对组件进行测试。
    • 在父组件中使用 Provider,它接收一个 value 属性,当 value 值发生变化时,它内部的所有消费组件都会重新渲染
      import StoreContext from './StoreContext'
      
      // 创建一个数据源
      const store = {
          name: 'Tom',
          age: 20
      }
      
      render() {
          return(
              <StoreContext.Provider value={store}>
                  <AAA></AAA>
              </StoreContext.Provider>
          )
      }
      
    • 在后代组件中使用 Consumer,它包裹一个函数,回调的参数正是距离自己最近的 Provider 提供的数据源(value);如果没有找到Provider,则回调 createContext() 的默认值;
      import StoreContext from './StoreContext'
      
      render() {
          return(<StoreContext.Consumer>
              {
                  value => {
                      return(<div>
                              <p>姓名: {value.name}</p>
                              <p>年龄: {value.age}</p>
                          </div>)
                  }
              }
          </StoreContext.Consumer>)
      }
      
      # 作为 props 直接把Consumer的值传递给组件
      render() {
          return(<StoreContext.Consumer>
              { ctx => <TitleBar {...ctx} /> }
          </StoreContext.Consumer>)
      }
      // 函数式组件
      function TitleBar(props) {
          console.log(props)
          return <div>TitleBar</div>
      }
      
    • Class.contextType 实现编程式消费最近Context的值,可以在任何生命周期中访问它
      import StoreContext from './StoreContext'
      class Titlebar extends React.Component {
          componentDidMount() {
              // StoreContext 共享的值被赋予 this.context
              let value = this.context;
          }
          render() {
              let value = this.context;
              // ...
          }
      }
      Titlebar.contextType = StoreContext;
      
  3. refs - 父组件主动获取子组件的数据
    React提供了 refs 管理组件标签上的自定义属性ref

    <Header ref="header" />
    
    this.refs.header  // 访问子组件的数据
    
  4. 发布订阅,第三方包如pubsub-js

  5. defaultPropspropTypes

    1. 父组件在调用子组件时,如果不给子组件传值,则子组件可以使用 defaultProps定义默认值;
      class Header extends React.Component { ... }
      Header.defaultProps = {
          msg: '0'  //如果父组件没有传递msg,则使用此默认值
      }
      export default Header;
      
    2. propTypes:用于验证父组件传值类型的合法性;
      import PropTypes from 'prop-types'  //react的一个内置模块
      Header.propTypes = {
          msg: PropTypes.number   //父组件传递msg的数据类型必须是number
      }
      
  6. 组件state/props的划分原则

    1. 让组件尽可能少状态
      对于只有UI渲染、数据展示、没有复杂交互的组件,应使用props,而不是state
    2. 组件随着时间产生变化的数据,界面有交互,应使用state

ref

  1. React v16.3引入了 React.createRef() 创建Ref,字符串类型的 Ref 将慢慢被抛弃;
    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.myRef = React.createRef();
        }
        render() {
            return <div ref={this.myRef} />;
        }
    }
    
    1. React 会在组件加载时设置refcurrent 属性,在卸载时则会改回 null
    2. ref 的更新会发生在钩子 componentDidMount()componentDidUpdate() 之前。
  2. 访问
    const node = this.myRef.current;
    
    current 的值取决于节点的类型
    • 普通HTML元素,值为DOM对象
    • React组件,值为实例对象
  3. 函数式组件上不能使用 ref 属性,因为它没有实例,但它的内部可以使用 ref 属性;
  4. React v16.3推荐使用 Ref 转发,从而把子组件的 Ref 暴漏给父组件;
  5. 回调式Ref
    这种设置ref的方式可以更加细致地控制 ref 的设置与解除。
    class CustomTextInput extends React.Component {
        constructor(props) {
            super(props);
            this.textInput = null;
        }
        this.setTextInputRef = element => {
            this.textInput = element;
        }
        render() {
            return <input type="text" ref={this.setTextInputRef} />
        }
    }
    
    • ref 属性接收一个函数,在组件挂载时回调 ref 函数,当卸载时传入 null 并回调;
    • 在组件间传递以回调的形式传递 refs
      class Parent extends React.Component {
          render() {
              return <CustomTextInput inputRef={el => this.inputElement = el} />
          }
      }
      
      function CustomTextInput(props) {
          return <input ref={props.inputRef} />
      }
      

生命周期

React V16.3之前的生命周期

V16.3之前的生命周期
生命周期
生命周期-before
  • this.forceUpdate() 手动强制触发组件的render渲染,会导致组件跳过shouldComponentUpdate(),直接调用render()
    应用场景:有些变量不在 state 上,或者 state 里的某个变量层次太深,更新时没有自动触发render()
  • React V16.0 引入新的生命周期:componentDidCatch(),如果 render() 函数抛出错误,该函数勾子可以捕捉到错误信息,且可以展示相应的错误提示。
    注:它只是一个增量式的修改,完全不影响原有的生命周期勾子。
  1. 组件首次加载过程中触发的函数
    constructor --> componentWillMount --> render --> componentDidMount
    
    • componentWillMount():组件将要挂载
    • render():数据/模板渲染
    • componentDidMount():组件加载完成
    • constructor()render()在组件加载时会被触发,但并不属于生命周期函数
  2. 组件状态更新过程中触发的函数
    shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
    
    • shouldComponentUpdate():是否更新组件,常用于做组件优化,返回 true 则更新,否则不更新。
       shouldComponentUpdate(nextProps, nextState) {
           // nextProps:表示父组件向当前组件传递的数据
           // nextState:当前组件更新后的数据,只是还没有重新渲染DOM
       }
      
    • componentWillUpdate():将要更新组件;
    • render():更新数据/模板;
    • componentDidUpdate():数据更新完成。
  3. 在父组件中改变 props 传值时触发的函数:componentWillReceiveProps(),然后才会触发组件更新的 render() 函数。
  4. 组件销毁时触发的函数:componentWillUnmount()
    1. 父组件
      this.state = { flag:true }
      setFlag() {
          this.setState({ flag:!this.state.flag })
      }
      render() {
          return(<div>
              { this.state.flag && <Header /> }
              <button onClick={this.setFlag}>挂载与销毁</button>
          </div>)
      }
      
    2. 子组件Header
      componentWillUnmount() {
          console.log('Header被销毁')
      }
      
  5. 手动移除某个容器上的组件:
    ReactDOM.unmountComponentAtNode(containerDom)
    

React V16.3的新生命周期

React V16.3 的更新中,除了被热烈讨论的新 Context API 之外,新引入的两个生命周期函数 static getDerivedStateFromProps、getSnapshotBeforeUpdate 以及在未来 V17.0 版本中即将被移除的三个生命周期函数 componentWillMount、componentWillReceiveProps、componentWillUpdate。这三个准备废弃的生命周期用 getDerivedStateFromProps 替代。如果仍想使用废弃的生命周期,可以加上前缀:UNSAFE_,如 UNSAFE_componentWillMount

React生命周期-after.png

原来的生命周期在 React v16 推出 Fiber 之后就不合适了,因为如果开启 async rendering,在 render() 之前的所有函数都可能被执行多次。
render() 之前执行的生命周期:componentWillMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate
除了 shouldComponentUpdate,其他三个都被静态函数getDerivedStateFromProps取代。
这种做法强制开发者在 render() 执行之前只做无副作用的操作,而且能做的操作局限在根据 propsstate 决定新的state

  • state getDerivedStateFromProps(props, state) 会在 render() 之前被调用,并且在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null 则不执行更新。
  • getSnapshotBeforeUpdate(prevProps, prevState)render() 之后、componentDidUpdate() 之前。使得组件能在发生更改之前从DOM中获取一些信息(如滚动位置)。返回值将作为参数snapshot传递给componentDidUpdate(prevProps, prevState, snapshot)

错误边界

默认清空下,若一个组件在渲染期间(render)发生错误,会导致整个组件树全部被卸载。
错误边界: 是一个组件,它能够捕获到渲染期间子组件发生的错误,并有能力阻止错误继续传播。
错误边界能捕获在渲染过程中 所有子组件的constructor和生命周期函数内发生的错误。

错误边界不能捕获的错误类型:

  • 发生在事件处理器里面的
  • 异步代码,如setTimeout、requestAnimationFrame
  • 服务端渲染
  • 自己本身抛出的错误

在代码层面上,只要一个类组件中定义了 static getDerivedStateFromError() 或者 componentDidCatch(),它就是一个错误边界(组件)。
一般来说,getDerivedStateFromError()是不允许发生副作用的,故而负责呈现一个备用的Fallback UI给用户;componentDidCatch()允许发生副作用,故负责打印错误日志,上报错误到远程服务器。

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }
 
    static getDerivedStateFromError(error) {
        // Update state so the next render will show the fallback UI.
        return { hasError: true };
    }
 
    componentDidCatch(error, info) {
        // You can also log the error to an error reporting service
        // error 表示被抛出的错误
        // info 表示一个含有 ```componentStack``` 属性的对象
        logErrorToMyService(error, info);
    }
 
    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return <h1>Something went wrong.</h1>;
        }
        
        return this.props.children; 
    }
}

然后可以把它当做一个普通的组件来用:

<ErrorBoundary>
    <MyWidget />
</ErrorBoundary>

<ErrorBoundary>使用起来就像try-catch语句,只不过它是用于React组件,且保留了React原生的声明特性。

注意: <ErrorBoundary>不能捕获自己所产生的错误,只能捕获在它之下的组件树所产生的错误。
<ErrorBoundary>嵌套使用的情况下,如果某个<ErrorBoundary>不能渲染一些错误信息(调用static getDerivedStateFromError()失败),那么这个错误就会往上冒泡到层级最近的<ErrorBoundary>。这也是try-catch语句在Javascript里的执行机制。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容