1.Context
定義:
一種可以在組件之間共享值的方式,不必顯示通過組件樹逐層傳遞props。
用法:
使用React.createContext創建一個context,再使用Provider將值傳遞給子組件,在子組件中指定contextType=創建的context,React 會往上找到最近的 theme Provider,使用時通過this.context即可獲取值。
缺點:
組件複用性變差。
注意事項:
傳遞對象爲對象時,檢測變化的方式會導致,每一次Privider重新渲染時,下面的組件都會重新渲染,因爲value屬性總被賦值新的對象,所以應該將value狀態提升到父節點的state裏。
衍生層層傳遞屬性方案:
比如只有最深的子組件需要知道最頂部組件的參數
1.組件組合:包含關係(chidren prop、jsx prop)、特例關係(把組件看作是其他組件的特殊實例),將最深的子組件自身傳遞下去,中間組件無需知道props,但是提升到最高層處理會使得高層組件變得更復雜,強行將底層組件適應這樣的形式。
2.context:向下廣播數據,所有組件都可以訪問到數據並能訪問到數據的更新,不受限於shouldComponentUpdate函數。
用法舉例:
// 只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效
const ThemeContext = React.createContext({ theme: 'light' });
class App extends React.Component {
render() {
return (
<Fragment>
// value屬性爲固定寫法
<ThemeContext.Provider value={{ theme: 'dark' }}>
<Toolbar />
</ThemeContext.Provider>
// 另一種方式:函數作爲子元素,接收當前的context值,返回React節點
<ThemeContext.Consumer>
{
value => <div>{value.theme}</div>
}
</ThemeContext.Consumer>
</Fragment>
);
}
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <button theme={this.context} />;
}
}
2.Portals
定義:
一種將子節點渲染到父組件以外的DOM節點的方式。
用法:
在組件的render中return ReactDOM.createPortals(child, container),參數同ReactDOM.render,可以用於視覺上彈出的容器,如對話框、提示框等。
注意事項:
雖然可以渲染到不同的節點,一個從portal內部會觸發的事件會一直冒泡至React樹的祖先。
例子:
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
3.高階組件
定義:
高階組件(HOC:higher order component)就是一個函數,接受一個組件作爲參數,並返回一個新的組件。高階組件不會修改原組件,也不會繼承複製原組件的行爲,相反,高階組件是通過將原組件包裹在容器組件裏的方式來組合使用原組件,是一個沒有副作用的純函數。
場景:
1.大部分實現邏輯相同的組件抽象成一個模式。
2.在組件中新增需求,可以通過反向繼承去繼承之前的組件。
例子:工作臺的簡單查詢頁流程大致相同,進入頁面請求默認數據,輸入查詢條件請求、分頁功能,可以將這些流程通過高階函數統一化。
形式:
1.屬性代理(props proxy):可以操作props,做自定義操作;抽離state,通過{props,cb}傳遞給WrappedComponent,再通過回調函數獲取state,也可以運用在處理表單的時候,在高階函數獲取到表單信息之後再去做其他的操作。
const connect = key => Com => {
return class connectComponent extends React.Component {
state = {
[key]: store[key]
}
}
componentDidMount() {
let that = this;
window.store = new Proxy(store, {
get: (target, key, receiver) => {
return Reflect.get(target, key, receiver);
},
set: (target, key, value, receiver) => {
that.setState({
[key]: value
})
return Reflect.set(target, key, value, receiver)
}
})
}
render() {
return <Com {...this.state} />
}
}
let store = {
name: 'myName'
}
@connect('age') // 相當於User = connect(User)('age')
class User extends React.Component {
render() {
return <div>{this.props.age}</div>
}
}
2.反向繼承(inheritance inversion):通過去繼承WrappedComponent,得到生命週期、state、function,也可以對WrappedComponent進行渲染劫持(hijack),控制他的render函數。
const loading = Com => {
showLoading() {}
hideLoading() {}
return class LoadingComponent extends Com {
// 不建議寫render,會讓組件越來越深,樣式也可能錯亂
render() {
return <div>
loading...
{super.render()}
</div>
}
}
}
@loading
class User extends React.Component {
componentDidMount() {
this.showLoading();
}
render() {
return <div>user</div>
}
}
注意事項:
1.不要在render函數中使用高階函數:render函數返回組件樹有差異會更新,如果不相等會完全卸載舊的子對象樹,重新加載一個組件會引起原有組件的所有狀態和子組件的丟失,如果每次返回的結果都不是一個引用,react認爲發生了變化。在組件定義外使用高階函數,可以試新組件只被定義一次,在渲染的過程中包保證都是同一個組件。
2.必須將原組件的靜態方法拷貝給新組件:因爲用高階組件包裝組件時,原組件會丟失所有靜態方法,可以使用hoist-non-react-statics來自動拷貝。
3.Refs屬性不能傳遞,因爲refs是一個僞屬性,高階組件創建的組件元素添加ref,指向的是最外層容器組件實例,而不是包裹組件,16.3版本添加了React.forwardRef來解決這個問題。
4. 高階組件不會修改子組件,也不拷貝子組件的行爲。
5.給HOC添加class名,方便調試。
4.render props
定義:
使用render作爲屬性傳入組件,可以共享狀態、封裝組件行爲等,動態決定需要渲染的組件。
例子:
多個組件都需要重用追蹤鼠標位置,但是作用不同,可以將渲染組件傳入行爲組件的render屬性中,爲行爲組件提供需要的參數。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}