React 组件化

React 组件化

  • 参考:王红元老师的React

组件化介绍

  • React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
    • 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
    • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);
    • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component);
  • 这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
    • 函数组件、无状态组件、展示型组件主要关注UI的展示;
    • 类组件、有状态组件、容器型组件主要关注数据逻辑;
  • 当然还有很多组件的其他概念:比如异步组件、高阶组件等。

类组件

  • 类组件的定义有如下要求:
    • 组件的名称是大写字符开头(无论类组件还是函数组件)
    • 类组件需要继承自 React.Component
    • 类组件必须实现render函数
  • 在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议我们使用ES6的class类定义。
  • 使用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函数返回一样的内容。
  • 函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):
    • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
    • 没有this(组件实例);
    • 没有内部状态(state);
    • 我们来定义一个函数组件

React 官方文档

认识生命周期

  • 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段;
    • 比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
    • 比如更新过程(Update),组件状态发生变化,重新更新渲染的过程;
    • 比如卸载过程(Unmount),组件从DOM树中被移除的过程;
  • React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
    • 比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调;
    • 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
    • 比如实现componentWillUnmount函数:组件即将被移除时,就会回调;
    • 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;
  • 我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(后面我们可以通过hooks来模拟一些生命周期的回调)

生命周期解析

生命周期函数

  • Constructor
  • 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
  • constructor中通常只做两件事情:
    • 通过给 this.state 赋值对象来初始化内部的state;
    • 为事件绑定实例(this);
  • componentDidMount
  • componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。
  • componentDidMount中通常进行哪里操作呢?
    • 依赖于DOM的操作可以在这里进行;
    • 在此处发送网络请求就最好的地方;(官方建议)
    • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
  • componentDidUpdate
    • componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
    • 当组件更新后,可以在此处对 DOM 进行操作;
    • 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
  • componentWillUnmount
    • componentWillUnmount() 会在组件卸载及销毁之前直接调用。
    • 在此方法中执行必要的清理操作;
    • 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

不常用生命周期函数

  • getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state; p getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
  • shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;
  • 另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用

认识组件的嵌套

  • 组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件;
  • 再将这些组件组合嵌套在一起,最终形成我们的应用程序;
  • 比如嵌套逻辑如下,它们存在如下关系:
    • App组件是Header、Main、Footer组件的父组件;
    • Main组件是Banner、ProductList组件的父组件;

import React, { Component } from 'react';

// Header
function Header() {
  return <h2>我是Header组件</h2>
}

// Main
function Banner() {
  return <h3>我是Banner组件</h3>
}

function ProductList() {
  return (
    <ul>
      <li>商品列表1</li>
      <li>商品列表2</li>
      <li>商品列表3</li>
      <li>商品列表4</li>
      <li>商品列表5</li>
    </ul>
  )
}

function Main() {
  return (
    <div>
      <Banner/>
      <ProductList/>
    </div>
  )
}

// Footer
function Footer() {
  return <h2>我是Footer组件</h2>
}


export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <Main/>
        <Footer/>
      </div>
    )
  }
}

认识组件间的通信

  • 父组件在展示子组件,可能会传递一些数据给子组件:
    • 父组件通过 属性=值 的形式来传递给子组件数据;
    • 子组件通过 props 参数获取父组件传递过来的数据;

父传子通信-类组件

import React, { Component } from 'react';


class ChildCpn extends Component {
  constructor() {
    super();
  }

  componentWillMount() {

  }

  componentDidMount() {
    console.log(this.props, "componentDidMount");
  }

  render() {
    // console.log(this.props, "render");
    const {name, age, height} = this.props;
    return (
      <h2>子组件展示数据: {name + " " + age + " " + height}</h2>
    )
  }
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age="18" height="1.88"/>
        <ChildCpn name="kobe" age="40" height="1.98"/>
      </div>
    )
  }
}

父传子通信-函数组件

import React, { Component } from 'react';

function ChildCpn(props) {
  const { name, age, height } = props;

  return (
    <h2>{name + age + height}</h2>
  )
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age="18" height="1.88" />
        <ChildCpn name="kobe" age="40" height="1.98" />
      </div>
    )
  }
}

父传子通信-属性验证

import React, { Component } from 'react';

import PropTypes from 'prop-types';

function ChildCpn(props) {
  const { name, age, height } = props;
  console.log(name, age, height);
  const { names } = props;

  return (
    <div>
      <h2>{name + age + height}</h2>
      <ul>
        {
          names.map((item, index) => {
            return <li>{item}</li>
          })
        }
      </ul>
    </div>
  )
}
//函数属性验证
ChildCpn.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  height: PropTypes.number,
  names: PropTypes.array
}

ChildCpn.defaultProps = {
  name: "why",
  age: 30,
  height: 1.98,
  names: ["aaa", "bbb"]
}

//类属性验证
class ChildCpn2 extends Component {
  // es6中的class fields写法
  static propTypes = {

  }

  static defaultProps = {

  }
}


export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age={18} height={1.88} names={["abc", "cba"]} />
        <ChildCpn name="kobe" age={40} height={1.98} names={["nba", "mba"]} />
        <ChildCpn />
      </div>
    )
  }
}

子传父通信-函数传递.

import React, { Component } from 'react';

class CounterButton extends Component {
  render() {
    const {onClick} = this.props;
    return <button onClick={onClick}>+1</button>
  }
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+</button>
        <CounterButton onClick={e => this.increment()} name="why"/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

组件通信示例

  •  TabControl.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class TabControl extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentIndex: 0
    }
  }

  render() {
    const { titles } = this.props;
    const {currentIndex} = this.state;

    return (
      <div className="tab-control">
        {
          titles.map((item, index) => {
            return (
              <div key={item} 
                   className={"tab-item " + (index === currentIndex ? "active": "")}
                   onClick={e => this.itemClick(index)}>
                <span>{item}</span>
              </div>
            )
          })
        }
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentIndex: index
    })

    const {itemClick} = this.props;
    itemClick(index);
  }
}

TabControl.propTypes = {
  titles: PropTypes.array.isRequired
}
  • App.js
import React, { Component } from 'react';

import TabControl from './TabControl';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.titles = ['新款', '精选', '流行'];

    this.state = {
      currentTitle: "新款",
      currentIndex: 0
    }
  }

  render() {
    const {currentTitle} = this.state;

    return (
      <div>
        <TabControl itemClick={index => this.itemClick(index)} titles={this.titles} />
        <h2>{currentTitle}</h2>
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentTitle: this.titles[index]
    })
  }
}

React实现slot

  • 两种方式:
  • NavBar.js
import React, { Component } from 'react'

export default class NavBar extends Component {
  render() {
    // this.props.children;
    return (
      <div className=" nav-bar">
        <div className="nav-item nav-left">
          {this.props.children[0]}
        </div>
        <div className="nav-item nav-center">
          {this.props.children[1]}
        </div>
        <div className="nav-item nav-right">
          {this.props.children[2]}
        </div>
      </div>
    )
  }
}
  • NavBar2.js
import React, { Component } from 'react'

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

    return (
      <div className="nav-bar">
        <div className="nav-item nav-left">
          {leftSlot}
        </div>
        <div className="nav-item nav-center">
          {centerSlot}
        </div>
        <div className="nav-item nav-right">
          {rightSlot}
        </div>
      </div>
    )
  }
}
  • App.js
import React, { Component } from 'react';

import NavBar from './NavBar';
import NavBar2 from './NavBar2';

export default class App extends Component {

  render() {
    const leftJsx = <span>aaa</span>;
    return (
      <div>
        <NavBar name="" title="" className="">
          <span>aaa</span>
          <strong>bbb</strong>
          <a href="/#">ccc</a>
        </NavBar>

        <NavBar2 leftSlot={leftJsx}
                 centerSlot={<strong>bbb</strong>}
                 rightSlot={<a href="/#">ccc</a>}/>
      </div>
    )
  }
}

跨组件的通信

跨组件通信-props

import React, { Component } from 'react';

function ProfileHeader(props) {
  return (
    <div>
      <h2>用户昵称: {props.nickname}</h2>
      <h2>用户等级: {props.level}</h2>
    </div>
  )
}

function Profile(props) {
  return (
    <div>
      {/* <ProfileHeader nickname={props.nickname} level={props.level}/> */}
      <ProfileHeader {...props}/>
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
} 

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    // const {nickname, level} = this.state;

    return (
      <div>
        <Profile {...this.state}/>
      </div>
    )
  }
}

跨组件通信-context

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

class ProfileHeader extends Component {
  render() {
    console.log(this.context);
    // jsx -> 
    return (
      <div>
        <h2>用户昵称: {this.context.nickname}</h2>
        <h2>用户等级: {this.context.level}</h2>
      </div>
    )
  }
}

ProfileHeader.contextType = UserContext;
ProfileHeader.contextType = ThemeContext;

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
        </UserContext.Provider>
        <Profile />
      </div>
    )
  }
}

跨组件通信-context函数

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

function ProfileHeader() {
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <div>
              <h2>用户昵称: {value.nickname}</h2>
              <h2>用户等级: {value.level}</h2>
            </div>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <Profile />
        </UserContext.Provider>
      </div>
    )
  }
}

跨组件通信-多个context

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

const ThemeContext = React.createContext({
  color: "black"
})

function ProfileHeader() {
  // jsx -> 嵌套的方式
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <ThemeContext.Consumer>
              {
                theme => {
                  return (
                    <div>
                      <h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
                      <h2>用户等级: {value.level}</h2>
                      <h2>颜色: {theme.color}</h2>
                    </div>
                  )
                }
              }
            </ThemeContext.Consumer>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <ThemeContext.Provider value={{ color: "red" }}>
            <Profile />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    )
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值