組件,從概念上相似於 JavaScript 函數。它接受任意的入參(即 「props」),並返回用於描述頁面展現內容的 React 元素。
React沒有多少API能夠用,基本上都是組件來完成一個項目。React的工做方式相似於UI=F(state)
,即一旦組件維護的state發生改變,render函數就會從新執行,致使試圖改變。所以學好React組件是相當重要的。javascript
咱們但願儘量讓更多的組件變成傻瓜組件,它只負責將數據展現到視圖上,而它所用的數據所有都提高到父級組件。父級組件負責邏輯處理和狀態維護,將子組件所需的回調事件和狀態經過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
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>
)
}
}
複製代碼
class Book extends PureComponent {
render () {
console.log('渲染了')
return (
<div> <p>書名:{this.props.name}</p> <p>價格:{this.props.price}</p> </div>
)
}
}
複製代碼
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>
)
}
}
複製代碼
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> ) } 複製代碼