react 進階
懶加載
React.lazy
函數能讓你像渲染常規組件一樣處理動態引入(的組件)。Suspense
加載指示器爲組件做優雅降級。fallback
屬性接受任何在組件加載過程中你想展示的 React 元素。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
Context
Context
提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法,設計目的是爲了共享那些對於一個組件樹而言是“全局”的數據。
// Context 可以讓我們無須明確地傳遍每一個組件,就能將值深入傳遞進組件樹。
// 爲當前的 theme 創建一個 context(“light”爲默認值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一個 Provider 來將當前的 theme 傳遞給以下的組件樹。
// 無論多深,任何組件都能讀取這個值。
// 在這個例子中,我們將 “dark” 作爲當前的值傳遞下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 讀取當前的 theme context。
// React 會往上找到最近的 theme Provider,然後使用它的值。
// 在這個例子中,當前的 theme 值爲 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
請謹慎使用,因爲這會使得組件的複用性變差。
API:
React.createContext
:
創建一個 Context 對象。當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當前的 context 值。只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。
Context.Provider
:
每個 Context 對象都會返回一個 Provider React 組件,它允許消費組件訂閱 context 的變化。當 Provider 的 value 值發生變化時,它內部的所有消費組件都會重新渲染。
Class.contextType
:
掛載在 class 上的 contextType 屬性會被重賦值爲一個由 React.createContext() 創建的 Context 對象。這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函數中。
Context.Consumer
:
這裏,React 組件也可以訂閱到 context 變更。這能讓你在函數式組件中完成訂閱 context。
Refs
Refs 提供了一種方式,允許我們訪問 DOM 節點或在 render 方法中創建的 React 元素。不能在函數組件上使用 ref 屬性,因爲他們沒有實例,可以在函數組件內部使用 ref 屬性。
適合使用 refs 的情況:
- 管理焦點,文本選擇或媒體播放。
- 觸發強制動畫。
- 集成第三方 DOM 庫。
使用方法:
-
創建 Refs
- Refs 是使用
React.createRef()
創建的,並通過ref
屬性附加到 React 元素。
- Refs 是使用
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
-
訪問 Refs
- 在 ref 的
current
屬性中被訪問
- 在 ref 的
const node = this.myRef.current;
Refs 轉發
Ref 轉發是一項將 ref 自動地通過組件傳遞到其一子組件的技巧。子組件使用React.forwardRef
接收ref。可用於Hoc
處理ref。
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接獲取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
上例中,FancyButton
使用 React.forwardRef
來獲取傳遞給它的 ref
,然後轉發到它渲染的 DOM button
。這樣,使用 FancyButton
的組件可以獲取底層 DOM 節點 button
的 ref
,並在必要時訪問,就像其直接使用 DOM button
一樣。
Fragments
Fragments 允許你將子列表分組,而無需向 DOM 添加額外節點。key 是唯一可以傳遞給 Fragment 的屬性
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
<React.Fragment></React.Fragment>
可簡寫爲<></>
高階組件(HOC)
HOC是參數爲組件,返回值爲新組件的函數。
HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 通過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有副作用。
示例:
// 此函數接收一個組件...
function withSubscription(WrappedComponent, selectData) {
// ...並返回另一個組件...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ...負責訂閱相關的操作...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ... 並使用新數據渲染被包裝的組件!
// 請注意,我們可能還會傳遞其他屬性
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
上例中class組件爲HOC的容器組件,容器組件擔任分離將高層和低層關注的責任,由容器管理訂閱和狀態,並將 prop 傳遞給處理渲染 UI。HOC 使用容器作爲其實現的一部分,你可以將 HOC 視爲參數化容器組件。
注意事項:
- 不要在 render 方法中使用 HOC。
在render中使用會導致diff 算法在對比組件變化時每次檢測都不一樣,每次渲染都會進行卸載,和重新掛載的操作,這不僅僅是性能問題 - 重新掛載組件會導致該組件及其所有子組件的狀態丟失。
-
務必複製靜態方法到容器組件上。
可以使用
hoist-non-react-statics
自動拷貝所有非 React 靜態方法import hoistNonReactStatic from 'hoist-non-react-statics'; function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} hoistNonReactStatic(Enhance, WrappedComponent); return Enhance; }
- Refs 不會被傳遞。
可用過Refs 轉發解決
常見的HOC:
redux的 connect
React.PureComponent
大部分情況下,你可以使用 React.PureComponent 來代替手寫 shouldComponentUpdate。只有當檢測數據是數組或對象時,由於淺拷貝的問題會導致比較出現偏差不能使用,此時使用深拷貝仍可繼續使用。
如以下代碼:
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
可替換爲:
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
Portals
Portal 提供了一種將子節點渲染到存在於父組件以外的 DOM 節點的優秀的方案。
一個 portal 的典型用例是當父組件有 overflow: hidden 或 z-index 樣式時,但你需要子組件能夠在視覺上“跳出”其容器。例如,對話框、懸浮卡以及提示框:
render() {
// React 並*沒有*創建一個新的 div。它只是把子元素渲染到 `domNode` 中。
// `domNode` 是一個可以在任何位置的有效 DOM 節點。
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
React.StrictMode
StrictMode 不會渲染任何可見的 UI。它爲其後代元素觸發額外的檢查和警告。嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。
作用:
- 識別不安全的生命週期
- 關於使用過時字符串 ref API 的警告
- 關於使用廢棄的 findDOMNode 方法的警告
- 檢測意外的副作用
- 檢測過時的 context API
原文git地址 覺得有用的話,來個star鼓勵,持續更新中。