React快速入门(二)组件与函数


React快速入门(二)组件与函数

React脚手架

  • 脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷
  • 创建:
    1. create-react-app 项目名称(全小写)
    2. cd 项目名称
    3. yarn start

React组件化开发

  • React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
    • 根据组件的定义方式,可以分为:函数组件(Functional Component )类组件(Class Component)
    • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )有状态组件(Stateful Component)
    • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)容器型组件(Container Component)
  • 最主要是关注数据逻辑和UI展示的分离:
    • 函数组件、无状态组件、展示型组件主要关注UI的展示
    • 类组件、有状态组件、容器型组件主要关注数据逻辑

类组件

  • 组件的名称是大写字符开头(无论类组件还是函数组件)

  • 类组件需要继承自React.Component

  • 类组件必须实现render函数

  • 使用class定义一个组件:

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

render函数的返回值

  • 当render被调用时,它会检查this.props和 this.state的变化并返回以下类型之—:
  • React元素:
    • 通常通过JSX创建
    • 例如:< div />会被React渲染为DOM节点,< MyComponent/>会被React渲染为自定义组件
    • 无论是< div/>还是< MyComponent />均为 React元素
  • 数组或 fragments:使得render方法可以返回多个元素
  • Portals:可以渲染子节点到不同的DOM子树中
  • 字符串或数值类型:它们在 DOM中会被渲染为文本节点
  • 布尔类型或 null:什么都不渲染

函数组件

  • 函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。
  • 函数组件有自己的特点:
    • 没有生命周期,也会被更新并挂载,但是没有生命周期函数
    • this关键字不能指向组件实例(因为没有组件实例);
    • 没有内部状态(state) ;

生命周期

在这里插入图片描述

  • Constructor

    • 如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。
    • constructor中通常只做两件事情:
      • 通过给this.state赋值对象来初始化内部的state
      • 为事件绑定实例(this)
  • componentDidMount

    • componentDidMount()会在组件挂载后(插入DOM树中)立即调用。
    • 依赖于DOM的操作可以在这里进行
    • 在此处发送网络请求最好的地方(官方建议)
    • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
  • componentDidUpdate

    • componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法。
    • 当组件更新后,可以在此处对 DOM进行操作;如果对更新前后的props进行了比较,也可以选择在此处进行网络请求
  • componentWillUnmount

    • componentWillUnmount()会在组件卸载及销毁之前直接调用
    • 在此方法中执行必要的清理操作;例如,清除 timer,取消网络请求或清除在componentDidMount()中创建的订阅等;
  • getDerivedStateFromProps: state的值在任何时候都依赖于props时使用;该方法返回—个对象来更新state;

  • getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的—些信息(比如说滚动位置);

  • shouldComponentUpdate:该生命周期函数很常用,做性能优化

React组件间通信

  • 父子组件
    • 父组件通过属性=值的形式来传递给子组件数据
    • 子组件通过props参数获取父组件传递过来的数据
//父组件
<MainBanner banners={banners} title="轮播图"/>

//子组件
import React, { Component } from 'react'
import PropTypes from "prop-types"

export class MainBanner extends Component {
  constructor(props) {
    super(props)

    this.state = {}
  }

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

    return (
      <div className='banner'>
        ...
      </div>
    )
  }
}

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

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

export default MainBanner
  • 子父组件
    • 通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可
//父组件
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>
        <AddCounter addClick={(count) => this.changeCounter(count)}/>
        <SubCounter subClick={(count) => this.changeCounter(count)}/>
      </div>
    )
  }
}
//子组件
export class AddCounter extends Component {
  addCount(count) {
    this.props.addClick(count)
  }

  render() {

    return (
      <div>
        <button onClick={e => this.addCount(1)}>+1</button>
        <button onClick={e => this.addCount(5)}>+5</button>
        <button onClick={e => this.addCount(10)}>+10</button>
      </div>
    )
  }
}

React中的插槽(slot)实现

  • 组件的children子元素

  • props属性传递React元素

//父组件
export class App extends Component {
  render() {
    const btn = <button>按钮2</button>

    return (
      <div>
        {/* 1.使用children实现插槽 */}
        <NavBar>
          <button>按钮</button>
          <h2>哈哈哈</h2>
          <i>斜体文本</i>
        </NavBar>

        {/* 2.使用props实现插槽 */}
        <NavBarTwo 
          leftSlot={btn}
          centerSlot={<h2>呵呵呵</h2>}
          rightSlot={<i>斜体2</i>}
        />
      </div>
    )
  }
}
//子组件一 -- 使用children 注意一个和多个时的children类型
export class NavBar extends Component {
  render() {
    const { children } = this.props
    console.log(children)

    return (
      <div className='nav-bar'>
        <div className="left">{children[0]}</div>
        <div className="center">{children[1]}</div>
        <div className="right">{children[2]}</div>
      </div>
    )
  }
}
//子组件二 -- 使用props
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>
    )
  }
}

Context应用场景

  • 如果层级很多的话,一层层传递是非常麻烦,并且代码是非常冗余的:

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

  1. React.createContext
    • 创建一个需要共享的Context对象;如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的Provider中读取到当前的context值(就近原则)
//defaultValue
const ThemeContext = React.createContext({ color: "blue", size: 10 })
const UserContext = React.createContext()
  1. Context.Provider
    • Provider接收一个value属性,传递给消费组件;一个Provider可以和多个消费组件有对应关系;多个Provider也可以嵌套使用,里层的会覆盖外层的数据;当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染
<UserContext.Provider value={{nickname: "kobe", age: 30}}>
	<ThemeContext.Provider value={{color: "red", size: "30"}}>
<Home {...info}/>
	</ThemeContext.Provider>
</UserContext.Provider>
  1. Class.contextType
    • 挂裁在class 上的contextType属性会被重赋值为一个ReactcreateContext()创建的Context对象,能使用this.context来消费最近Context 上的那个值
HomeInfo.contextType = ThemeContext
  1. Context.Consumer
    • 使用:当使用value的组件是一个函数式组件时;当组件中需要使用多个Context时
export class HomeInfo extends Component {
  render() {
    return (
      <div>
        <h2>HomeInfo: {this.context.color}</h2>
        <UserContext.Consumer>
          {
            value => {
              return <h2>Info User: {value.nickname}</h2>
            }
          }
        </UserContext.Consumer>
      </div>
    )
  }
}
  • 全局事件总线event-bus,eventBus.emit,eventBus.on,eventBus.off

setState原理

  • setState方法是从Component中继承过来的监听数据变化的方法
  • setState的三种写法:
changeText() {
    // 1.setState基本使用
    this.setState({
      message: "你好啊, 李银河"
    })

    // 2.setState可以传入一个回调函数
    // 好处一: 可以在回调函数中编写新的state的逻辑
    // 好处二: 当前的回调函数会将之前的state和props传递进来
    this.setState((state, props) => {
      // 1.编写一些对新的state处理逻辑
      // 2.可以获取之前的state和props值
      console.log(this.state.message, this.props)
      return {
        message: "你好啊, 李银河"
      }
    })

    // 3.setState在React的事件处理中是一个异步调用
    // 如果希望在数据更新之后(数据合并), 获取到对应的结果执行一些逻辑代码
    // 那么可以在setState中传入第二个参数: callback
    this.setState({ message: "你好啊, 李银河" }, () => {
      console.log("++++++:", this.state.message)
    })
    console.log("------:", this.state.message)
  }
  • setState更新是异步的,可以显著的提升性能
    • 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新
  • 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
    • state和props不能保持—致性,会在开发中产生很多的问题

React更新机制

在这里插入图片描述

  • 发生改变时React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
  • React对这个算法进行了优化,将其优化成了O(n):
    • 同层节点之间相互比较,不会跨节点比较;
    • 不同类型的节点,产生不同的树结构;
    • 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定
  • shouldComponentUpdate
    • 该方法有两个参数:
      • 参数一: nextProps修改之后,最新的props属性
      • 参数二: nextState修改之后,最新的state属性
    • 该方法返回值是一个boolean类型:
      • 返回值为true,那么就需要调用render方法
      • 返回值为false,那么就不需要调用render方法
      • 默认返回的是true,也就是只要state发生改变,就会调用render方法;
  • shouldComponentUpdate -> PureComponent(类组件,内部自动对状态进行判断)
    • memo(函数组件);const Profile = memo(function(props) {}

使用ref

  1. 传入字符串
  • 使用时通过this.refs.传入的字符串格式获取对应的元素
  1. 传入一个对象
  • 对象是通过React.createRef()方式创建出来的;使用时获取到创建的对象其中有一个current属性就是对应的元素;
  1. 传入一个函数
  • 该函数会在DOM被挂载时进行回调,这个函数会传入一个元素对象,我们可以自己保存;使用时,直接拿到之前保存的元素对象即可
export class App extends PureComponent {
  constructor() {
    super()
    this.state = {}
    this.titleRef = createRef()
    this.titleEl = null
  }
  getNativeDOM() {
    // 1.方式一: 在React元素上绑定一个ref字符串
    console.log(this.refs.why)
    // 2.方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素
    console.log(this.titleRef.current)
    // 3.方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入
    console.log(this.titleEl)
  }

  render() {
    return (
      <div>
        <h2 ref="why">Hello World</h2>
        <h2 ref={this.titleRef}>你好啊,李银河</h2>
        <h2 ref={el => this.titleEl = el}>你好啊, 师姐</h2>
        <button onClick={e => this.getNativeDOM()}>获取DOM</button>
      </div>
    )
  }
}
  • ref 的值根据节点的类型而有所不同:
    • 当ref属性用于HTML元素时,构造函数中使用React.createRef()创建的 ref 接收底层DOM元素作为其current属性
    • 当ref属性用于自定义 class组件时,ref对象接收组件的挂载实例作为其current属性
    • 不能在函数组件上使用ref 属性,因为他们没有实例,使用React.forwardRef
      • const HelloWorld = forwardRef(function(props, ref) {}

受控/非受控组件

受控组件

  • 在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState()来更新
    • 我们将两者结合起来,使React的state成为“唯—数据源”
    • 渲染表单的React 组件还控制着用户输入过程中表单发生的操作
    • 被React 以这种方式控制取值的表单输入元素就叫做“受控组件
  • 基础:
export class App extends PureComponent {
  constructor() {
    super()
    this.state = {
      username: "coderwhy"
    }
  }
    
  inputChange(event) {
    console.log("inputChange:", event.target.value)
    this.setState({ username: event.target.value })
  }

  render() {
    const { username } = this.state
    return (
      <div>
        {/* 受控组件 */}
        <input type="checkbox" value={username} onChange={e => this.inputChange(e)}/>
        {/* 非受控组件 */}
        <input type="text" />
        <h2>username: {username}</h2>
      </div>
    )
  }
}
  • 进阶
export class App extends PureComponent {
  constructor() {
    super()
    this.state = {
      username: "",
      password: "",
      isAgree: false,
      hobbies: [
        { value: "sing", text: "唱", isChecked: false },
        { value: "dance", text: "跳", isChecked: false },
        { value: "rap", text: "rap", isChecked: false }
      ],
      fruit: ["orange"]
    }
  }
  handleSubmitClick(event) {
    // 1.阻止默认的行为
    event.preventDefault()
    // 2.获取到所有的表单数据, 对数据进行处理
    console.log("获取所有的输入内容")
    console.log(this.state.username, this.state.password)
    const hobbies = this.state.hobbies.filter(item => item.isChecked).map(item => item.value)
    console.log("获取爱好: ", hobbies)
    // 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
  }
  handleInputChange(event) {
    this.setState({
      [event.target.name]: event.target.value
    })
  }
  handleAgreeChange(event) {
    this.setState({ isAgree: event.target.checked })
  }
  handleHobbiesChange(event, index) {
    const hobbies = [...this.state.hobbies]
    hobbies[index].isChecked = event.target.checked
    this.setState({ hobbies })
  }
  handleFruitChange(event) {
    const options = Array.from(event.target.selectedOptions)
    const values = options.map(item => item.value)
    this.setState({ fruit: values })
    // 额外补充: Array.from(可迭代对象)
    // Array.from(arguments)
    const values2 = Array.from(event.target.selectedOptions, item => item.value)
    console.log(values2)
  }

  render() {
    const { username, password, isAgree, hobbies, fruit } = this.state
    return (
      <div>
        <form onSubmit={e => this.handleSubmitClick(e)}>
          {/* 1.用户名和密码 */}
          <div>
            <label htmlFor="username">
              用户: 
              <input 
                id='username' 
                type="text" 
                name='username' 
                value={username} 
                onChange={e => this.handleInputChange(e)}
              />
            </label>
            <label htmlFor="password">
              密码: 
              <input 
                id='password' 
                type="password" 
                name='password' 
                value={password} 
                onChange={e => this.handleInputChange(e)}
              />
            </label>
          </div>
          {/* 2.checkbox单选 */}
          <label htmlFor="agree">
            <input 
              id='agree' 
              type="checkbox" 
              checked={isAgree} 
              onChange={e => this.handleAgreeChange(e)}
            />
            同意协议
          </label>
          {/* 3.checkbox多选 */}
          <div>
            您的爱好:
            {
              hobbies.map((item, index) => {
                return (
                  <label htmlFor={item.value} key={item.value}>
                    <input 
                      type="checkbox"
                      id={item.value} 
                      checked={item.isChecked}
                      onChange={e => this.handleHobbiesChange(e, index)}
                    />
                    <span>{item.text}</span>
                  </label>
                )
              })
            }
          </div>
          {/* 4.select */}
          <select value={fruit} onChange={e => this.handleFruitChange(e)} multiple>
            <option value="apple">苹果</option>
            <option value="orange">橘子</option>
            <option value="banana">香蕉</option>
          </select>
          <div>
            <button type='submit'>注册</button>
          </div>
        </form>
      </div>
    )
  }
}

非受控组件

  • 使用非受控组件,表单数据交由DOM节点来处理,使用ref来获取,使用defaultValue来设置默认值

高阶函数

  • 满足以下条件之一:接受一个或多个函数做为输入,输出一个函数
  • 高阶组件(HOC)官方定义:高阶组件是参数为组件,返回值为新组件的函数
// 定义一个高阶组件
function hoc(Cpn) {
  // 1.定义类组件
  class NewCpn extends PureComponent {
    render() {
      return <Cpn name="why"/>
    }
  }
  return NewCpn

  // 定义函数组件
  // function NewCpn2(props) {

  // }
  // return NewCpn2
}

class HelloWorld extends PureComponent {
  render() {
    return <h1>Hello World</h1>
  }
}

const HelloWorldHOC = hoc(HelloWorld)

export class App extends PureComponent {
  render() {
    return (
      <div>
        <HelloWorldHOC/>
      </div>
    )
  }
}
  • 应用:装饰器模式,用于增强现有组件的功能
  1. props的增强–给一些需要特殊数据的组件, 注入props
  2. 利用高阶组件来共享Context
  3. 渲染判断鉴权
  4. 生命周期劫持
  • 早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用:

    • Mixin可能会相互依赖,相互耦合,不利于代码维护
    • 不同的Mixin中的方法可能会相互冲突
    • Mixin非常多时,组件处理起来会比较麻烦,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
  • HOC也有自己的一些缺陷:

    • HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难
    • HOC可以劫持props,在不遵守约定的情况下也可能造成冲突

Portals/fragment/StrictMode

  • Portal(类似Vue3中Teleport)提供了一种将子节点渲染到存在于父组件以外的DOM节点的优秀的方案:
    • 第一个参数(child)是任何可渲染的React子元素,例如一个元素,字符串或fragment
    • 第二个参数(container)是一个DOM元素

{createPortal(<h2>App H2</h2>, document.querySelector("#why")) }

  • 我们希望可以不渲染这样一个div则使用Fragment(类似Vue中的telemplate)
    • Fragment 允许将子列表分组,而无需向DOM添加额外节点
    • 如果我们需要在Fragment中添加key,那么就不能使用短语法<></>
<Fragment>
	<h2>{item.title}</h2>
	<p>{item.content}</p>
</Fragment>
  • StrictMode是一个用来突出显示应用程序中潜在问题的工具:
    • 与Fragment一样,StrictMode不会渲染任何可见的Ul
    • 它为其后代元素触发额外的检查和警告;
    • 严格模式检查仅在开发模式下运行;它们不会影响生产构建
<StrictMode>
	<Home/>
</StrictMode>
  1. 识别不安全的生命周期
  2. 使用过时的ref APl
  3. 检查意外的副作用
    • 这个组件的constructor会被调用两次
    • 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
    • 在生产环境中,是不会被调用两次的
  4. 使用废弃的findDOMNode方法
    • 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了
  5. 检测过时的context APl
    • 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会思想的苇草i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值