當初要是看了這篇,React高階組件早會了。
概況:
什么是高階組件?
高階部件是一種用於復用組件邏輯的高級技術,它並不是 React API的一部分,而是從React 演化而來的一種模式。 具體地說,高階組件就是一個接收一個組件並返回另外一個新組件的函數!
這是官方文檔說的,我沒有截全,因為后面的解釋會造成誤解,但簡單講高階組件(函數)就好比一個加工廠,同樣的,屏幕、cpu、揚聲器、鍵盤按鍵、外殼、電池,小米手機工廠組裝完就是小米手機,魅族手機組裝完就是魅族手機,基本材料都是相同的,不同工廠(高階組件)有不同的實現及產出,當然這個工廠(高階組件)也可能是針對某個基本材料的處理。
總之產出的結果擁有了輸入組件不具備的功能,輸入的組件可以是一個組件的實例,也可以是一個組件類,還可以是一個無狀態組件的函數。
解決什么問題?
隨着項目越來越復雜,開發過程中,多個組件需要某個功能,而且這個功能和頁面並沒有關系,所以也不能簡單的抽取成一個新的組件,但是如果讓同樣的邏輯在各個組件里各自實現,無疑會導致重復的代碼。比如頁面有三種彈窗一個有title,一個沒有,一個又有右上角關閉按鈕,除此之外別無它樣,你總不能整好幾個彈窗組件吧,這里除了tilte,關閉按鈕其他的就可以做為上面說的基本材料。
高階組件總共分為兩大類
- 代理方式
- 操縱prop
- 訪問ref(不推薦)
- 抽取狀態
- 包裝組件
- 繼承方式
- 操縱生命周期
- 操縱prop
代理方式之 操縱prop
刪除prop
import React from 'react'
function HocRemoveProp(WrappedComponent) {
return class WrappingComPonent extends React.Component {
render() {
const { user, ...otherProps } = this.props;
return <WrappedComponent {...otherProps} />
}
}
}
export default HocRemoveProp;
增加prop
接下來我把簡化了寫法,把匿名函數去掉,同時換成箭頭函數
import React from 'react'
const HocAddProp = (WrappedComponent,uid) =>
class extends React.Component {
render() {
const newProps = {
uid,
};
return <WrappedComponent {...this.props} {...newProps} />
}
}
export default HocAddProp;
上面HocRemoveProp高階組件中,所做的事情和輸入組件WrappedComponent功能一樣,只是忽略了名為user的prop。也就是說,如果WrappedComponent能處理名為user的prop,這個高階組件返回的組件則完全無視這個prop。
const { user, ...otherProps } = this.props;
這是一個利用es6語法技巧,經過上面的語句,otherProps里面就有this.props中所有的字段除了user.
假如我們現在不希望某個組件接收user的prop,那么我們就不要直接使用這個組件,而是把這個組件作為參數傳遞給HocRemoveProp,然后我們把這個函數的返回結果當作組件來使用
兩個高階組件的使用方法:
const newComponent = HocRemoveProp(SampleComponent);
const newComponent = HocAddProp(SampleComponent,'1111111');
也可以利用decorator語法糖這樣使用
import React, { Component } from 'React';
@HocRemoveProp
class SampleComponent extends Component {
render() {}
}
export default SampleComponent;
代理方式之 抽取狀態
將所有的狀態的管理交給外面的容器組件,這個模式就是 抽取狀態
外面的容器就是這個高階組件
const HocContainer = (WrappedComponent) =>
class extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
}
onNameChange = (event) => {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {...this.props} {...newProps} />
}
}
@HocContainer
class SampleComponent extends React.Component {
render() {
return <input name="name" {...this.props.name}/>
}
}
這樣當我們在使用這個已經被包裹的input組件(SampleComponent)時候
它的值就被放在了HocContainer高階組件中,當很多這樣的input組件都用這個HocContainer高階組件時,那么它們的值都將保存在這個HocContainer高階組件中
代理方式之 包裝組件
const HocStyleComponent = (WrappedComponent, style) =>
class extends React.Component {
render() {
return (
<div style={style}>
<WrappedComponent {...this.props} {...newProps} />
</div>
)
}
}
這樣使用
import HocStyleComponent from './HocStyleComponent';
const colorSytle ={color:'#ff5555'}
const newComponent = HocStyleComponent(SampleComponent, colorSytle);
-代理方式的生命周期的過程類似於堆棧調用:
didmount 一> HOC didmount 一>(HOCs didmount) 一>(HOCs will unmount) 一>HOC will unmount一>unmount
在說繼承方式之前先看一個例子
const MyContainer = (WrappedComponent) =>
class extends WrappedComponent {
render() {
return super.render();
}
}
這個例子很簡單,相當於把WrappedComponent組件的render方法,通過super.render()方法吐到了MyContainer 中,可以順序調用。
-
繼承方式的生命周期的過程類似於隊列調用:
didmount 一> HOC didmount 一>(HOCs didmount) 一>will unmount一>HOC will unmount一> (HOCs will unmount) -
代理方式下WrappedComponent會經歷一個完整的生命周期,產生的新組件和參數組件是兩個不同的組件,一次渲染,兩個組件都會經歷各自的生命周期,
-
在繼承方式下,產生的新組件和參數組件合二為一,super.render只是生命周期中的函數,變成一個生命周期。
來看下面的例子你就會明白了。
繼承方式之 操縱生命周期(渲染劫持)
首先創建一個高階,在創建一個使用高階組件的組件,也就是是輸入組件,最后我在改變這個輸入組件props
import * as React from 'react';
const HocComponent = (WrappedComponent) =>
class MyContainer extends WrappedComponent {
render() {
if (this.props.time && this.state.success) {
return super.render()
}
return <div>倒計時完成了...</div>
}
}
這個高階組件會直接讀取輸入組件中的props,state,然后控制了輸入組件的render展示
只有在props.time和state.success同時為真的時候才會展示
import * as React from 'react';
import HocComponent from './HocComponent'
@HocComponent
class DemoComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
success: true,
};
}
render() {
return <div>我是一個組件</div>
}
}
export default DemoComponent;
然后調用,遞減time數值直到變為0
最后頁面的效果就是,當然他不是循環的。先展示”我是一個組件“,我設置了兩秒,之后展示”倒計時完成“.
由此可以看出高階組件也可以控制state
但是最好要限制這樣做,可能會讓WrappedComponent組件內部狀態變得一團糟。建議可以通過重新命名state,以防止混淆。
繼承方式之 操縱prop
const HOCPropsComponent = (WrappedComponent) =>
class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {
color: (elementsTree && elementsTree.type === 'div') ? '#fff' : '#ff5555'
};
const props = Object.assign({}, elementsTree.props, newProps)
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
return newElementsTree
}
}
這樣就傳入了新的props,。
React.cloneElement( element, [props], [...children])
參數:TYPE(ReactElement),[PROPS(object)],[CHILDREN(ReactElement)]
克隆並返回一個新的 ReactElement ,新返回的元素會保留有舊元素的 props、ref、key,也會集成新的 props。
還有一個方式,在傳遞props上有着強於高階組件的優勢不用關心命名,
class addProps extends React.Component {
render() {
const newProps = 'uid'
return this.props.children(newProps)
}
}
使用方式
<addProps>
{
(argument) => <div>{argument}</div>
}
</addProps>
感覺很方便,但是每次渲染都會重新定義一個新的函數,如果不想的話就不要定義匿名函數,
showUid(argument) {
return <div>{argument}</div>
}
彩蛋recompose庫
recompose是一個很流行的庫,它提供了很多很有用的高階組件(小工具),而且也可以優雅的組合它們。
Step 1 扁平props.
我們有這樣一個組件
const Profile = ({ user }) => (
<div>
<div>Username: {user.username}</div>
<div>Age: {user.age}</div>
</div>
)
如果想要改變組件接口來接收單個 prop 而不是整個用戶對象,可以用 recompose 提供的高 階組件 flattenProp 來實現。
const Profile = ({ username,age }) => (
<div>
<div>Username: {username}</div>
<div>Age: {age}</div>
</div>
)
const ProfileWithFlattenUser = flattenProp('user')(Profile);
現在我們希望同時使用多個高階組件:一個用於扁平化處理用戶 prop,另一個用於重命名用 戶對象的單個 prop,不過串聯使用函數的做法似乎不太好。 此時 recompose 庫提供的 compose 函數就派上用場了。
const enhance = compose(
flattenProp('user'),
renameProp('username', 'name')
)
然后按照以下方式將它應用於原有組件:
const EnhancedProfile = enhance(Profile)
還可以將 compose 函數用 在我們自己的高階組件上,甚至結合使用都可以:
const enhance = compose(
flattenProp('user'),
renameProp('username', 'name'),
withInnerWidth
)
Step 2 提取輸入表單的State
我們將從Recompose庫中使用withStateHandlers高階組件。 它將允許我們將組件狀態與組件本身隔離開來。 我們將使用它為電子郵件,密碼和確認密碼字段添加表單狀態,以及上述字段的事件處理程序。
import { withStateHandlers, compose } from "recompose";
const initialState = {
email: { value: "" },
password: { value: "" },
confirmPassword: { value: "" }
};
const onChangeEmail = props => event => ({
email: {
value: event.target.value,
isDirty: true
}
});
const onChangePassword = props => event => ({
password: {
value: event.target.value,
isDirty: true
}
});
const onChangeConfirmPassword = props => event => ({
confirmPassword: {
value: event.target.value,
isDirty: true
}
});
const withTextFieldState = withStateHandlers(initialState, {
onChangeEmail,
onChangePassword,
onChangeConfirmPassword
});
export default withTextFieldState;
withStateHandlers它接受初始狀態和包含狀態處理程序的對象。調用時,每個狀態處理程序將返回新的狀態。
好了,很辛苦也很感謝你能看到這里,關於recompose介紹到此為止,喜歡的朋友可以深入研究recompose其它的方法和源碼。
不准確的地方歡迎拍磚