React(2)之——React组件化

  组件,从概念上相似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展现内容的 React 元素。
  React没有多少API能够用,基本上都是组件来完成一个项目。React的工做方式相似于UI=F(state),即一旦组件维护的state发生改变,render函数就会从新执行,致使试图改变。所以学好React组件是相当重要的。javascript

容器组件VS展现组件

  咱们但愿尽量让更多的组件变成傻瓜组件,它只负责将数据展现到视图上,而它所用的数据所有都提高到父级组件。父级组件负责逻辑处理和状态维护,将子组件所需的回调事件和状态经过props传递给子组件。这样单纯的展现组件就会有很好的易用性、复用性和维护性。vue

import React, { Component } from 'react'

//容器组件
export default class BookList extends Component{
    constructor(props){
        super(props)
        this.state = {
            books:[]
        }
    }
    componentDidMount() {
        setTimeout(() => {
         this.setState({
            books:[
            { name: '《你不知道的javascript》', price: 50 },
            { name: '《ES6标准入门》', price: 99 } 
            ]
         })
        }, 1000)
    }
    render(){
        return(
            <div> {this.state.books.map((book, index) => <Book key={index} {...book}></Book>)} </div>
        )
    }
}

//展现组件
function Book({ name, price }){
    return (
        <div> <p>书名:{name}</p> <p>价格:{price}</p> </div>  
    )
}
复制代码

展现组件存在的问题

  上述展现组件存在一个问题,由于React中数据维持着不变性的原则,只要setState改变books,都会触发render函数的调用以及虚拟DOM的比较。若是books中的值只有一条变化,它也会引起每条数据都引起一次render函数调用。咱们怎么去规避展现组件无谓的数据消耗和渲染函数的调用呢?首先咱们看这段代码:java

import React, { Component, PureComponent } from 'react'

//容器组件
export default class BookList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      books: []
    }
  }
  componentDidMount () {
    setTimeout(() => {
      this.setState({
        books: [
          { name: '《你不知道的javascript》', price: 50 },
          { name: '《ES6标准入门》', price: 99 }
        ]
      })
    }, 1000)
    setTimeout(() => {
      this.setState({
        books: [
          { name: '《哈利波特》', price: 25 },
          { name: '《ES6标准入门》', price: 99 }
        ]
      })
    }, 2000)
  }
  render () {
    return (
      <div> {this.state.books.map((book, index) => <Book key={index} {...book}></Book>)} </div>
    )
  }
}
// 展现组件
function Book ({ name, price }) {
  console.log('渲染了')
  return (
    <div> <p>书名:{name}</p> <p>价格:{price}</p> </div>
  )
}
复制代码

  它在浏览器上打的log以下: react


  首先由一个空数组变成有两条数据的数组确定会致使两次render函数调用,可是第二次变化时,只有第一条数据改变,但仍是引发了两次render函数的调用。这是由于咱们为了维持数据的不变性,每次都会更新books为一个全新的数组。
  咱们要明确一点,确定是要维持数据的不变性的。有三种方法能够规避这种无谓的render的调用。在PureComponent没有出现以前,咱们在shouldComponentUpdate这个生命周期钩子函数中比较下一个值和当前值,若是相等,则不须要更新该条数据,返回false,写法比较累赘。React15.3以后出现了PureComponent纯组件,它就是在内部实现了在shouldComponentUpdate中比较值。还有一种是React16.6以后出现的React.memo,与使用PureComponent方法的原理和效果是等价的,它是一个高阶组件。
  用上述方法在浏览器上打的log为:

shouldComponentUpdate
class Book extends Component {
  shouldComponentUpdate ({ name, price }) {
    if (this.props.name === name && this.props.price === price) {
      return false
    }
    return true
  }
  render () {
    console.log('渲染了')
    return (
      <div> <p>书名:{this.props.name}</p> <p>价格:{this.props.price}</p> </div>
    )
  }
}
复制代码
PureComponent
class Book extends PureComponent {
  render () {
    console.log('渲染了')
    return (
      <div> <p>书名:{this.props.name}</p> <p>价格:{this.props.price}</p> </div>
    )
  }
}
复制代码
React.memo
const Book = React.memo(
  function ({ name, price }) {
    console.log('渲染了')
    return (
      <div> <p>书名:{name}</p> <p>价格:{price}</p> </div>
    )
  }
)
复制代码

高阶组件

  上面咱们讲到了高阶组件,它是一个函数,接收一个组件,返回一个增强后的组件。组件是将 props 转换为 UI,而高阶组件是将组件转换为另外一个组件。高阶组件能够提升组件的复用率,重写生命周期函数。
  基础写法:webpack

import React, { Component } from 'react'
//一个简单的展现组件
function Pure (props) {
  return (
    <div>{props.name} -- {props.age}</div>
  )
}
//高阶组件
const withLog = (Comp) => {
  console.log(Comp.name + '渲染了')
  return (props) => {
    return <Comp {...props}></Comp>
  }
}
//生成一个新的组件
const NewPure = withLog(Pure)
//使用这个新组件
export default class Hoc extends Component {
  render () {
    return (
      <div> <NewPure age='19' name='zhunny'></NewPure> </div>
    )
  }
}
复制代码

高阶组件的链式调用

  高阶组件能够链式调用,且能够在一个链式调用中调用屡次同一个高阶组件。web

import React, { Component } from 'react'

function Pure (props) {
  return (
    <div>{props.name} -- {props.age}</div>
  )
}

const strengthenPure = Comp => {
  const name = 'zhunny'
  //返回类组件
  return class extends React.Component {
    componentDidMount () {
      console.log('do something')
    }
    render () {
      return <Comp {...this.props} name={name}></Comp>
    }
  }
}

const withLog = (Comp) => {
  console.log(Comp.name + '渲染了')
  return (props) => {
    return <Comp {...props}></Comp>
  }
}

//高阶组件能够链式调用
const NewPure = withLog(strengthenPure(withLog(Pure)))
export default class Hoc extends Component {
  render () {
    return (
      <div> <NewPure age='19'></NewPure> </div>
    )
  }
}
复制代码

高阶组件的装饰器写法

  ES7的装饰器能够简化高阶组件的写法,不过须要引入一个转义decorator语法的插件,并在根目录配置config-overrides.js文件。安装react-app-rewired取代react-scripts,能够扩展webpack的配置 ,相似vue.config.jsnpm

npm install --save-dev babel-plugin-transform-decorators-legacy
npm install react-app-rewired@2.0.2-next.0 babel-plugin-import --save
复制代码
const { injectBabelPlugin } = require("react-app-rewired");
module.exports = function override (config, env) {
  //装饰器
  config = injectBabelPlugin(
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    config
  );

  return config;
};
复制代码

  由于decorator只能装饰类,所以只能装饰基于类的组件。数组

@withLog
@strengthenPure
@withLog
class Pure extends React.Component {
  render () {
    return (
      <div>{this.props.name} -- {this.props.age}</div>
    )
  }
}
复制代码

组件的组合composition

  React 有十分强大的组合模式。咱们推荐使用组合而非继承来实现组件间的代码重用。组合组件给予你足够的敏捷去定义自定义组件的外观和行为,并且是以一种明确和安全的方式进行。若是组件间有公用的非UI逻辑,将它们抽取为JS模块导入使用而不是继承它。浏览器

包含关系

  有些组件没法提早知晓它们子组件的具体内容。这些组件能够使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中。安全

function Dialog (props) {
  return (<div style={{ border: `1px solid ${props.color || "blue"}` }}> {props.children} <footer>{props.footer}</footer> </div>)
}

//能够看做一个特殊的Dialog
function WelcomeDialog (props) {
  console.log(props)
  return (
    <Dialog {...props}> {/*相似于匿名slot插槽*/} <h1>欢迎光临</h1> <p>感谢使用React</p> </Dialog>
  )
}

export default function () {
  //footer相似于具名slot插槽
  const footer = <button onClick={() => { alert('1') }}>footer</button>
  return (
   <WelcomeDialog color='green' footer={footer}></WelcomeDialog>
  )
}
复制代码

  props.children能够是任意js表达式,能够是一个函数。

const Api = {
  getUser () {
    return { name: 'jerry', age: 20 }
  }
}

function Fetcher (props) {
  const user = Api[props.name]()
  return props.children(user)
}
export default function () {
  //相似于做用域插槽
  return (
   <Fetcher name="getUser"> {({ name, age }) => ( <p> {name}-{age} </p> )} </Fetcher>
  )
}
复制代码

示例

function GroupRadio (props) {
  //由于props的内容是不可修改的,所以在Radio上增长一个属性须要拷贝一份
  return <div> {React.Children.map(props.children, child => { return React.cloneElement(child, { name: props.name }) })} </div>
}

function Radio ({ children, ...rest }) {
  return (
    <label> <input type="radio" {...rest} /> {children} </label> ) } export default function () { return ( <GroupRadio name="mvvm"> <Radio value="vue">vue</Radio> <Radio value="angular">angular</Radio> <Radio value="react">react</Radio> </GroupRadio> ) } 复制代码
相关文章
相关标签/搜索