React 学习笔记 - 组件基础

179 阅读6分钟

组件

创建组件的方式

  1. createClass
    • 最原始的方法,现在已经在 v16+ 移除
    // v16已经移除此方法
    const createHeader = React.createClass({
      getInitialState: function() {
        return { name: 'createClass' }
      },
      render: function() {
        return <div>{this.state.name}:{this.props.title}</div>
      }
    })
    const title = 'React'
    
    ReactDOM.render(
      <div>
        <createHeader title={title}/>
      </div>,
      document.getElementById('root')
    );
    
  2. Component
    • 使用 ES6 的新语法来编写组件类,使用 class 关键字继承 React.Component 来生成一个组件类
    class Header extends Component {
      constructor(props) {
        super(props)
        this.state = {
          name: 'class Component'
        }
      }
      render() {
        return <div>{this.state.name}:{this.props.title}</div>
      }
    }
    const title = 'React'
    
    ReactDOM.render(
      <div>
        <Header title={title}/>
      </div>,
      document.getElementById('root')
    );
    
  3. Function Component
    • 一般用于编写 无状态 的组件,自身没有状态,依赖于传入的 props 来决定渲染
    const FuncHeader = ({ title }) => {
      const name = 'Function'
      return <div>{name}:{title}</div>
    }
    const title = 'React'
    
    ReactDOM.render(
      <div>
        <FuncHeader title={title}/>
      </div>,
      document.getElementById('root')
    );
    
  4. PureComponent
    • 继承 React.PureComponent ,只会对属性和状态进行浅比较, 直接比较是否相等,如果是引用类型,由于浅比较,所以并不会更新组件,有一定的性能优化作用
    //普通组件,只要调用 render 无论变化与否,必定更新
    class FakeHeader extends Component {
      constructor(props) {
        super(props)
        this.state = {
          name: 'class Component FakeHeader'
        }
      }
      render() {
        const { title, data:{ title: dataTitle } } = this.props
        const { name } = this.state
        const str = `${name} - dataTitle:${dataTitle} - title:${title}`
        console.log(`update - ${str}`)
        return <div>{str}</div>
      }
    }
    //官方 PureComponent 只有在浅比较下数据变化,更新组件
    class PureHeader extends PureComponent {
      constructor(props) {
        super(props)
        this.state = {
          name: 'class PureComponent'
        }
      }
      render() {
        const { title, data:{ title: dataTitle } } = this.props
        const { name } = this.state
        const str = `${name} - dataTitle:${dataTitle} - title:${title}`
        console.log(`update - ${str}`)
        return <div>{str}</div>
      }
    }
    //模拟实现 纯组件 
    class FakePureHeader extends Component {
      constructor(props) {
        super(props)
        this.state = {
          name: 'class FakePureHeader'
        }
      }
      shouldComponentUpdate(nextProps, nextState){
        const { props, state } = this
        const shallowCompare = (a, b) => a === b || Object.keys(a).every(k => a[k] === b[k])
        // 浅比较数据更新才更新
        return !shallowCompare(nextProps, props) || !shallowCompare(nextState, state)
      }
      render() {
        const { title, data:{ title: dataTitle } } = this.props
        const { name } = this.state
        const str = `${name} - dataTitle:${dataTitle} - title:${title}`
        console.log(`update - ${str}`)
        return <div>{str}</div>
      }
    }
    
    const title = 'React'
    const data = {
      title
    }
    
    const render = (data, title) => {
      ReactDOM.render(
        <div>
          {/* <createHeader title={title}/> */}
          <Header title={title}/>
          <FuncHeader title={title}/>
          <FakeHeader data={data} title={title}/>
          <PureHeader data={data} title={title}/>
          <FakePureHeader data={data} title={title}/>
        </div>,
        document.getElementById('root')
      );
    }
    // update - class Component FakeHeader - dataTitle:React - title:React
    // update - class PureComponent - dataTitle:React - title:React
    // update - class FakePureHeader - dataTitle:React - title:React
    render(data, title)
    setTimeout(() => {
      data.title = 'update'
      // update - class Component FakeHeader - dataTitle:update - title:React
      // 改变的是引用值 PureComponent 和 FakePureHeader 不会更新
      render(data, title)
    }, 1000);
    setTimeout(() => {
      // update - class Component FakeHeader - dataTitle:update - title:title
      // update - class PureComponent - dataTitle:update - title:title
      // update - class FakePureHeader - dataTitle:update - title:title
      // 改变的是基本类型,更新组件
      render(data, 'title')
    }, 3000);
    

组件生命周期

挂载

  • 旧版本
    1. constructor:初始化组件
    2. componentWillMount:组件挂载之前
    3. render:渲染组件
    4. componentDidMount:组件挂载结束
  • 新版本
    1. constructor
    2. getDerivedStateFromProps:静态方法! render 之前调用,返回一个 state 更新组件,返回 null 则不更新,无法获取到实例!!!
    3. render
    4. componentDidMount

更新

父组件更新
  • 旧版本
    1. componentWillReceiveProps:已挂载的组件接收新的 props 之前被调用
    2. shouldComponentUpdate:返回一个 Boolean 确定是否更新组件
    3. componentWillUpdate:组件更新之前调用
    4. render
    5. componentDidUpdate:组件更新结束后调用
  • 新版本
    1. getDerivedStateFromProps
    2. shouldComponentUpdate
    3. render
    4. getSnapshotBeforeUpdate:在最近一次渲染输出(提交到 DOM 节点)之前调用。
    5. componentDidUpdate
状态更新
  • 旧版本
    1. shouldComponentUpdate
    2. componentWillUpdate
    3. render
    4. componentDidUpdate
  • 新版本
    1. getDerivedStateFromProps
    2. shouldComponentUpdate
    3. render
    4. getSnapshotBeforeUpdate
    5. componentDidUpdate

卸载

  1. componentWillUnmount:组件卸载时调用
class Header extends Component {
  constructor(props) {
    console.log('constructor...')
    super(props)
    this.state = {
      name: 'Header'
    }
  }
  static getDerivedStateFromProps(props, state){
    console.log('getDerivedStateFromProps...')
    return state
  }
  componentWillMount() {
    console.log('componentWillMount...')
  }
  componentWillReceiveProps(){
    console.log('componentWillReceiveProps...')
  }
  shouldComponentUpdate(nextProps, nextState){
    console.log('shouldComponentUpdate...')
    return true
  }
  componentWillUpdate(nextProps, nextState){
    console.log('componentWillUpdate...')
    console.log(nextProps, nextState)
  }
  handleClick(){
    this.setState({
      name: this.state.name + `~`
    })
  }
  render() {
    console.log('render...')
    return ()
  }
  getSnapshotBeforeUpdate(prevProps, prevState){
    console.log('getSnapshotBeforeUpdate...')
  }
  componentDidUpdate(prevProps, prevState, snapshot){
    console.log('componentDidUpdate...')
  }
  componentDidMount(){
    console.log('componentDidMount...')
  }
  componentWillUnmount(){
    console.log('componentWillUnmount...')
  }
}

const title = 'React'
const render = (title) => {
  ReactDOM.render(
    <div>
      {title && <Header title={title}/>}
    </div>,
    document.getElementById('root')
  );
}
// 首次挂载
// constructor
// componentWillMount
// render
// componentDidMount

// 新版本的生命周期
// constructor
// getDerivedStateFromProps
// render
// componentDidMount
render(title)
// 组件内部更新 state
// shouldComponentUpdate
// componentWillUpdate
// render
// componentDidUpdate

// 新版本的生命周期
// getDerivedStateFromProps
// shouldComponentUpdate
// render
// getSnapshotBeforeUpdate
// componentDidUpdate
setTimeout(() => {
  // 父组件更新
  // componentWillReceiveProps
  // shouldComponentUpdate
  // componentWillUpdate
  // render
  // componentDidUpdate

  // 新版本的生命周期
  // getDerivedStateFromProps
  // shouldComponentUpdate
  // render
  // getSnapshotBeforeUpdate
  // componentDidUpdate
  render('title')
}, 1000);
setTimeout(() => {
  // 移除组件 
  // componentWillUnmount
  render()
}, 3000);

属性 props

属性 props 对于组件来说是只读的,只能又父组件改变属性来更新子组件,数据流是单向的。

children 组件表示当前组件的子组件集合

属性可以通过设置 defaultProps 来做属性的默认值,通过引入 prop-types 来做属性入参的类型限制

状态 state

constructor 中初始化,或者直接组件中 state = {} 通过调用 this.setState 来更新状态

setState

  1. 异步:合成事件和生命周期钩子调用
  2. 同步:原生事件和 setTimeout 调用
  3. 批量更新:异步执行的时候,会把多次调用合成一次,只执行一次更新
  4. 函数式调用:this.setState((state, props) => newState,callback?)
class Header extends Component {
  static defaultProps = {
    title: 'default'
  }
  constructor(props) {
    super(props)
    this.state = {
      name: 'Header'
    }
  }
  handleClick(){
    this.setState({
      name: this.state.name + '~'
    })
  }
  render() {
    return <div onClick={e => this.handleClick()}>{this.state.name}:{this.props.title}</div>
  }
}

const render = (title) => {
  ReactDOM.render(
    <div>
      <Header/>
      <Header title={title}/>
    </div>,
    document.getElementById('root')
  );
}
render('')
setTimeout(() => {
  render('title')
}, 1000);

组件和事件

React 事件的特点

  1. 事件命名是驼峰式命名法,例如 onClick
  2. 事件处理器是一个函数,不是字符串。例如 onClick={this.handleClick}
  3. 声明式,不需要经跟选择器绑定事件
  4. 事件代理,React 只在根元素上绑定事件,所有的事件响应都通过事件代理响应
  5. 返回经过封装的 事件对象,兼容性更好,原生的事件对象可以通过 e.nativeEvent 访问
class Count extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    this.handleClickMinus = this.handleClickMinus.bind(this)
  }
  handleClickPlus(){
    this.setState({
      count: this.state.count + 1
    })
  }
  handleClickMinus(){
    this.setState({
      count: this.state.count - 1
    })
  }
  render() {
    const { count } = this.state
    return <div>
      <span>count:{count}</span>
      <button onClick={e => this.handleClickPlus(e)}>+</button>
      <button onClick={this.handleClickMinus}>-</button>
    </div>
  }
}

const render = () => {
  ReactDOM.render(
    <div>
      <Count/>
    </div>,
    document.getElementById('root')
  );
}
render()

组件通信

父子组件

  1. 父传子
    • 通过属性 props 来完成 <Child title={title}/>
  2. 子传父
    • 通过回调函数 或者 引入发布订阅消息的类,通过消息发布订阅来实现传递消息
class Child extends Component {
  handleClick(){
    this.props.onClick('Child')
  }
  render(){
    const { name } = this.props
    return <div onClick={e => this.handleClick(e)}>
      msg from {name}
    </div>
  }
}

class Parent extends Component {
  constructor(props){
    super(props)
    this.state = {
      name: 'parent'
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(name){
    this.setState({
      name
    })
  }
  render(){
    const { name } = this.state
    return <div>
      {name}
      <Child onClick={this.handleClick} name={name}/>
    </div>
  }
}
ReactDOM.render(
    <div>
      <Parent/>
    </div>,
    document.getElementById('root')
);

爷孙组件

爷孙通信可以通过 React 提供的 context 来通信,一般传递一些只读的信息,类似于全局变量,滥用和容易改变让程序变得不可预测。

class Child extends Component {
  // 声明需要读取 context 上的数据
  static contextTypes = {
    text: PropTypes.string,
    changeText: PropTypes.func
  }
  handleClick(){
    this.props.onClick('Child')
    //最好不要通过子组件去改变祖先组件的状态,这样会让状态变得不可预测
    this.context.changeText('child') 
  }
  render(){
    const { name } = this.props
    // 获取 context 上的数据
    const { text } = this.context
    return <div onClick={e => this.handleClick(e)}>
      msg from {name} {text}
    </div>
  }
}

class Parent extends Component {
  constructor(props){
    super(props)
    this.state = {
      name: 'parent'
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(name){
    this.setState({
      name
    })
  }
  render(){
    const { name } = this.state
    return <div>
      {name}
      <Child onClick={this.handleClick} name={name}/>
    </div>
  }
}

class Ancestor extends Component {
  constructor(props){
    super(props)
    this.state = {
      name: 'Ancestor'
    }
  }
  // 声明 要在 context 上放的数据
  static childContextTypes = {
    text: PropTypes.string,
    changeText: PropTypes.func
  }
  handleText(name){
    this.setState({
      name
    })
  }
  // 祖先组件往 context 放东西
  getChildContext(){
    return { text: this.state.name, changeText: this.handleText.bind(this) }
  }
  render(){
    const { name } = this.state
    const { children } = this.props
    return <div>
      {name} 
      {children}
    </div>
  }
}

const render = () => {
  ReactDOM.render(
    <div>
      <Ancestor>
        <Parent/>
      </Ancestor>
    </div>,
    document.getElementById('root')
  );
}
render()

兄弟组件

通过共同的父组件作为桥梁实现两个组件的通信

class Count1 extends Component {
  handleClick(){
    const { sum, onClick } = this.props
    onClick(sum + 1)
  }
  render(){
    const { sum } = this.props
    return <button onClick={(e)=>this.handleClick(e)}>{sum}</button>
  }
}

class Count2 extends Component {
  handleClick(){
    const { sum, onClick } = this.props
    onClick(sum - 1)
  }
  render(){
    const { sum } = this.props
    return <button onClick={(e)=>this.handleClick(e)}>{sum}</button>
  }
}

class CountBox extends Component {
  constructor(props){
    super(props)
    this.state = {
      sum: 0
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(sum){
    this.setState({
      sum
    })
  }
  render(){
    const { sum } = this.state
    return <div>
      sum:{sum}
      <Count1 sum={sum} onClick={this.handleClick}/>
      <Count2 sum={sum} onClick={this.handleClick}/>
    </div>
  }
}

const render = () => {
  ReactDOM.render(
    <div>
      <CountBox/>
    </div>,
    document.getElementById('root')
  );
}
render()

任意组件

一般使用状态管理工具 Redux 来集中统一处理