React 从零到高级 02-组件化&组件之间通讯(建议从Vue转React 同学一定要详细阅读)

94 阅读7分钟

类组件

类组件的定义有如下要求:

  • 组件的名称是大写字符开头(无论类组件还是函数组件)
  • 类组件需要继承自 React.Component
  • 类组件必须实现render函数

使用class定义一个组件:

  • constructor是可选的,我们通常在constructor中初始化一些数据;
  • this.state中维护的就是我们组件内部的数据(页面渲染需要的数据);
  • render() 方法是 class 组件中唯一必须实现的方法;

render函数的返回值

当 render 被调用时,它会检查 this.props 和 this.state(通过setState函数改变) 的变化并返回以下类型

  • React 元素(通常通过 JSX 创建。也就是通过 Babel编译成react.createElement)
  • 数组或者fragments [123,243] 会对数组进行遍历 然后依次显示出来
  • Portals 可以渲染子节点到不同Dom树中
  • 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
  • 布尔类型或 null:什么都不渲染。

函数组件

函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。

函数组件有自己的特点(有了hooks 就可以完美解决这些问题)

  • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
  • this关键字不能指向组件实例(因为没有组件实例);
  • 没有内部状态(state)

生命周期

最常用的生命周期函数图

image.png

  • componentDidMount()(组件被渲染到DOM: 被挂载到DOM)
  1. 依赖于DOM操作可以在这里面进行
  2. 发送网络请求
  3. 可以在这处订阅一些东西(事件总线)会在componentWillUnmount取消订阅
  • componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法
  1. 当组件更新后,可以在此处对 DOM 进行操作
  2. 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网 络请求)。
  • componentWillUnmount 会在组件卸载及销毁之前直接调用
  1. 在此方法中执行必要的清理操作;
  2. 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

不常用的生命周期

  • getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象 来更新state;

  • getSnapshotBeforeUpdate:在React更新DOM 之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);可以在componentDidUpdate钩子中获取到他返回的参数

  • shouldComponentUpdate:该生命周期函数很 常用,返回一个true/false 来是否进行渲染

import React from "react"

class HelloWorld extends React.Component {
  // 1.构造方法: constructor
  constructor() {
    console.log("HelloWorld constructor")
    super()
    this.state = {
      message: "Hello World"
    }
  }

  changeText() {
    this.setState({ message: "你好啊, 李银河" })
  }

  // 2.执行render函数
  render() {
    console.log("HelloWorld render")
    const { message } = this.state

    return (
      <div>
        <h2>{message}</h2>
        <p>{message}是程序员的第一个代码!</p>
        <button onClick={e => this.changeText()}>修改文本</button>
      </div>
    )
  }

  // 3.组件被渲染到DOM: 被挂载到DOM
  componentDidMount() {
    console.log("HelloWorld componentDidMount")
  }

  // 4.组件的DOM被更新完成: DOM发生更新
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("HelloWorld componentDidUpdate:", prevProps, prevState, snapshot)
  }

  // 5.组件从DOM中卸载掉: 从DOM移除掉
  componentWillUnmount() {
    console.log("HelloWorld componentWillUnmount")
  }


  // 不常用的生命周期补充
  shouldComponentUpdate() {
    return true
  }

  getSnapshotBeforeUpdate() {
    console.log("getSnapshotBeforeUpdate")
    return {
      scrollPosition: 1000
    }
  }
}

export default HelloWorld

组件间的通讯

父传子

  • 父组件通过 属性=值 的形式来传递给子组件数据;
  • 子组件通过 props 参数获取父组件传递过来的数据;
  • 可以通过 prop-types 库来进行参数验证
  • 可以使用defaultProps库 来进行参数默认值的设置
export class MainBanner extends Component {
// 可以把默认值写成类中的 static defaultProps 对传入进来的参数赋默认值
  // static defaultProps = {
  //   banners: [],
  //   title: "默认标题"
  // }

// 当类中没有内部数据state的时候 可以补血constructor 他内部自动给this赋props
  constructor(props) {
    super(props)

    this.state = {}
  }

  render() {
    // console.log(this.props)
    const { title, banners } = this.props

    return (
      <div className='banner'>
        <h2>封装一个轮播图: {title}</h2>
        <ul>
          {
            banners.map(item => {
              return <li key={item.acm}>{item.title}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

// MainBanner传入的props类型进行验证
MainBanner.propTypes = {
  banners: PropTypes.array.isRequired,
  title: PropTypes.string
}

// MainBanner传入的props的默认值
MainBanner.defaultProps = {
  banners: [],
  title: "默认标题"
}

子传父

  • 通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
子组件
export class SubCounter extends Component {
  subCount(count) {
    this.props.subClick(count)
  }

  render() {
    return (
      <div>
      // 通过箭头函数 给subCount方法传递参数
        <button onClick={e => this.subCount(-1)}>-1</button>
        <button onClick={e => this.subCount(-5)}>-5</button>
        <button onClick={e => this.subCount(-10)}>-10</button>
      </div>
    )
  }
}

// 夫组件

export class App extends Component {
  constructor() {
    super()

    this.state = {
      counter: 100
    }
  }

  changeCounter(count) {
    this.setState({ counter: this.state.counter + count })
  }

  render() {
    const { counter } = this.state

    return (
      <div>
        <h2>当前计数: {counter}</h2>
        <SubCounter subClick={(count) => this.changeCounter(count)}/>
      </div>
    )
  }
}

React中的插槽(slot)

  • 组件中的children子组件
  • props属性传递React元素
  1. children方式实现插槽 注意事项 从props获取到children 写在组件闭合标签内部的元素。 如果他是多个元素 它就返回一个数组 如果只有一个元素 他就返回元素对象
  • 数组:通过数组索引进行使用(比较麻烦)
  • 对象 :直接赋值 在react 源码之中createElemnt函数中 他接收的children 参数 会通过arguments来判断children参数长度
  • 如果等于1的话,那就直接赋值给对象
  • 如果大于1的话,那就会形成新的数组 把children逐步push里面

   <NavBar>
          <button>按钮</button>
          <h2>哈哈哈</h2>
          <i>斜体文本</i>
  </NavBar>
  
  export class NavBar extends Component {
  render() {
    const { children } = this.props
    return (
      <div className='nav-bar'>
        <div className="left">{children[0]}</div>
        <div className="center">{children[1]}</div>
        <div className="right">{children[2]}</div>
      </div>
    )
  }
}

补充点 :在给组件传递参数时候 可以用属性扩展 React网址:zh-hans.reactjs.org/docs/jsx-in…

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}
他俩是等价的 
function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;}
  1. props实现插槽 给组件属性传入一个元素

  <NavBarTwo 
          leftSlot={btn}
          centerSlot={<h2>呵呵呵</h2>}
          rightSlot={<i>斜体2</i>}
        />


export class NavBarTwo extends Component {
  render() {
    const { leftSlot, centerSlot, rightSlot } = this.props

    return (
      <div className='nav-bar'>
        <div className="left">{leftSlot}</div>
        <div className="center">{centerSlot}</div>
        <div className="right">{rightSlot}</div>
      </div>
    )
  }
}

// 可以使用箭头函数来进行作用域插槽(插槽的传递参数)

  getTabItem(item) {
    if (item === "流行") {
      return <span>{item}</span>
    } else if (item === "新款") {
      return <button>{item}</button>
    } else {
      return <i>{item}</i>
    }
  }

  render() {
    return (
      <div className='app'>
        <TabControl 
          // itemType={item => <button>{item}</button>}
          itemType={item => this.getTabItem(item)}
        />
      </div>
    )
  }
  
      return (
      <div className='tab-control'>
        {
          titles.map((item, index) => {
            return (
              <div 
                className={`item ${index === currentIndex?'active':''}`} 
                key={item}
                onClick={e => this.itemClick(index)}
              >
                {/* <span className='text'>{item}</span> */}
                {itemType(item)}
              </div>
            )
          })
        }
      </div>
    )

非父子组件数据共享

  • React提供了一个API:Context;
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是 “全局”的数据,例如当前认证的用户、主题或首选语言;
context相关API
  • React.createContext

    1. 创建一个需要共享的Context对象:
    2. 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
    3. defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
  • Context.Provider

    1. 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化
    2. Provider 接收一个 value 属性,传递给消费组件;
    3. 一个 Provider 可以和多个消费组件有对应关系;
    4. 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
    5. 当 Provider 的value 值发生变化时,它内部的所有消费组件都会重新渲染;
  • Class.contextType

    1. 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
    2. 这能让你使用 **this.context **来消费最近 Context 上的那个值;
    3. 你可以在任何生命周期中访问到它,包括 render 函数中;
  • Context.Consumer

    1. 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context
    2. 这里需要 函数作为子元素(function as child)这种做法;
    3. 这个函数接收当前的 context 值,返回一个 React 节点;
  • 什么时候使用Context.Consumer呢?

    1. 当使用value的组件是一个函数式组件时;
    2. 当组件中需要使用多个Context时;
  {/* 第二步操作: 通过ThemeContext中Provider中value属性为后代提供数据 */}
  // 1.创建Context
const ThemeContext = React.createContext({ color: "blue", size: 10 })

const UserContext = React.createContext()


       <UserContext.Provider value={{nickname: "kobe", age: 30}}>
         <ThemeContext.Provider value={{color: "red", size: "30"}}>
           <Home {...info}/>
         </ThemeContext.Provider>
       </UserContext.Provider>
       
       {/* 没有被context所包裹的 就会使用默认值*/}
       <Profile/>
       
       
       
 export class HomeInfo extends Component {
 render() {
   // 4.第四步操作: 获取数据, 并且使用数据
   console.log(this.context)

   return (
     <div>
       <h2>HomeInfo: {this.context.color}</h2>
       <UserContext.Consumer>
         {
           value => {
             return <h2>Info User: {value.nickname}</h2>
           }
         }
       </UserContext.Consumer>
     </div>
   )
 }
}

// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext