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> ) } 複製代碼
相關文章
相關標籤/搜索