一文詳解 React 組件類型
本文的目標是讓開發者清晰地了解 React 組件類型,哪些在現代 React 應用中依然在使用,以及為何一些類型現在不再使用了。
作者 | Robin Wieruch
譯者 | 彎月
責編 | 屠敏
出品 | CSDN(ID:CSDNNews)
以下為譯文:
儘管React從2013年發布到現在並沒有引入太多重大改變,但不同類型的React組件也出現了不少。一些組件類型和組件設計模式今天依然在使用,它們已成了構建React應用程序的標準,而另一些類型的組件只會在舊的應用和新手教學中出現。
在這篇文章中,我想通過層次化的方式向初學React的人介紹一下不同的React組件和React設計模式。讀完本文後,你應當能夠從舊的應用和入門文章中分辨出不同類型的React組件,並能夠自信地編寫自己的現代React組件。
React createClass組件
我們要從React的createClass組件說起。createClass方法為開發者提供了一個工廠方法,無需編寫JavaScript類就可以創建React類組件。在JavaScript ES6出現之前,這種方法是創建React組件的標準方法,因為在JavaScript ES5時代還無法使用類語法:
var App = React.createClass({
getInitialState: function() {
return {
value: "",
};
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React "createClass" Component!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
createClass()工廠方法接受一個對象,該對象定義了React組件中的方法。其中,getInitialState()方法用來設置React組件的初始狀態,還有必須的render()方法用來顯示JSX形式的組件。給對象傳遞更多函數即可添加更多的「方法」(如onChange())。
還可以使用生命周期方法來管理副作用。例如,為了隨時將輸入框中的值保存到瀏覽器的local storage中,可以利用componentDidUpdate()這個生命周期方法,只需要將該函數傳遞給工廠函數的對象即可。而且,local storage中的值也可以在組件接收初始狀態的時候讀出來:
var App = React.createClass({
getInitialState: function() {
return {
value: localStorage.getItem("myValueInLocalStorage") || "",
};
},
componentDidUpdate: function() {
localStorage.setItem("myValueInLocalStorage", this.state.value);
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React "createClass" Component!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
每次重新載入或刷新瀏覽器時,之前在輸入框中輸入過的、保存在local storage中的初始狀態就會在組件初次mount的時候顯示出來。
注意:React的createClass方法現在已經不在React的核心包中了。如果你想嘗試下,就必須要安裝另一個包:npm install create-react-class。
React Mixin
React Mixin是在React提出可重用組件邏輯的高級設計方式時加入的。利用Mixin可以將React組件中的邏輯提取出來作為獨立的對象使用。在使用Mixin對象時,Mixin中的所有功能都會被引入到組件中:
var localStorageMixin = {
getInitialState: function() {
return {
value: localStorage.getItem("myValueInLocalStorage") || "",
};
},
setLocalStorage: function(value) {
localStorage.setItem("myValueInLocalStorage", value);
},
};
var App = React.createClass({
mixins: [localStorageMixin],
componentDidUpdate: function() {
this.setLocalStorage(this.state.value);
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React "createClass" Component with Mixin!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
在這個例子中,Mixin提供了組件的初始狀態,而該初始狀態是從local storage中讀取的,並且還利用setLocalStorage()擴展原來的組件,該函數之後會在組件中被調用。為了讓Mixin更靈活,我們可以使用一個返回函數的對象:
function getLocalStorageMixin(localStorageKey) {
return {
getInitialState: function() {
return { value: localStorage.getItem(localStorageKey) || "" };
},
setLocalStorage: function(value) {
localStorage.setItem(localStorageKey, value);
},
};
}
var App = React.createClass({
mixins: [getLocalStorageMixin("myValueInLocalStorage")],
...
});
不過,現代React應用程序已經不再使用Mixin了,因為它們會帶來一些負面作用。關於Mixin的細節和消亡過程可以閱讀這裡(https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)
React類組件
React類組件是在JavaScript ES6時引入的,因為直到ES6才支持JS類。有時候它們也被稱為React ES6類組件。至少有了JavaScript ES6之後,就不需要使用React的createClass方法了。JS自己終於支持類了:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
};
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.target.value });
}
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
使用JavaScript類編寫的React Component有個類似於類構造器的方法,主要用於讓React設置初始狀態,或者綁定方法。還有必須的render方法用於返回JSX的輸出。React組件的所有內部邏輯都通過類組件定義中的面向對象繼承,從extends React.Component獲得。但是,除了這種用法之外,我並不推薦進一步使用類繼承,相反,應當主要使用類組合(composition)。
注意:利用JavaScript類定義React組件時還可以使用了另一種語法,通過JavaScript ES6的箭頭函數來自動綁定React組件中的方法:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
};
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
React類組件提供幾個生命周期方法,用於mount、update和unmount等。比如前面的local storage的例子,可以在生命周期方法中以副作用的方式來執行這些操作——即,將輸入框中的最新值保存到local storage中,而在構造函數中可以根據local storage的值來設置初始狀態:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem("myValueInLocalStorage") || "",
};
}
componentDidUpdate() {
localStorage.setItem("myValueInLocalStorage", this.state.value);
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
利用this.state、this.setState()和生命周期方法,React類組件中的狀態管理和副作用可以寫在一起。React類組件到現在依然在廣泛使用,儘管後文即將介紹的React函數組件在現代React應用程序中得到了更廣泛的應用,因為函數組件已經不遜於類組件了。
React高階組件
React高階組件(Higher-Order Components,簡稱 HOC)是一種React的高級設計模式,是替代Mixin的另一種在組件間復用邏輯的方法。如果你沒聽說過HOC,可以讀一讀我的另一篇入門文章:高階組件(https://www.robinwieruch.de/gentle-introduction-higher-order-components/)。簡單來說,高階組件就是接收一個組件作為輸入,然後輸出另一個組件(並擴展其功能)的組件。我們利用前面的例子來看看,怎樣才能將功能提取到可復用的高階組件中。
const withLocalStorage = localStorageKey => Component =>
class WithLocalStorage extends React.Component {
constructor(props) {
super(props);
this.state = {
[localStorageKey]: localStorage.getItem(localStorageKey),
};
}
setLocalStorage = value => {
localStorage.setItem(localStorageKey, value);
};
render() {
return (
<Component
{...this.state}
{...this.props}
setLocalStorage={this.setLocalStorage}
/>
);
}
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { value: this.props["myValueInLocalStorage"] || "" };
}
componentDidUpdate() {
this.props.setLocalStorage(this.state.value);
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>
Hello React ES6 Class Component with Higher-Order Component!
</h1>
<input
value={this.state.value}
type="text"
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
const AppWithLocalStorage = withLocalStorage("myValueInLocalStorage")(App);
另一個高級React設計模式就是React Render Prop組件,通常代替React高階組件使用。我不在給出這種抽象的例子,更多內容請查看網上的一些教程。
React高階組件和React Render Prop組件在今天都在被廣泛使用,儘管React函數組件和React鉤子(後文會介紹)也許對於React組件的抽象更好。不過,高階組件和Render Prop也可以用在函數組件上。
React函數組件
React函數組件等價於React類組件,但它表現為一個函數,而不是一個類。過去函數組件沒有狀態也無法使用副作用,因此它們被稱為「無狀態函數組件」,但自從React鉤子出現後,函數組件就復活了。
React鉤子給函數組件帶來了狀態和副作用。React不僅帶有各種內置的鉤子,還允許創建自定義的鉤子。我們來看看前面的類組件的例子怎樣改寫成函數組件:
const App = () => {
const [value, setValue] = React.useState("");
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type="text" onChange={onChange} />
<p>{value}</p>
</div>
);
};
這段代碼僅在輸入框上演示了函數組件。由於要捕獲輸入框的值,就需要使用組件狀態,因此這裡用到了內置的React.useState鉤子。
React鉤子還可以在函數組件中實現副作用。一般來說,內置的useEffect鉤子可以用來在任何props或state發生變化時執行一個函數:
const App = () => {
const [value, setValue] = React.useState(
localStorage.getItem("myValueInLocalStorage") || "",
);
React.useEffect(() => {
localStorage.setItem("myValueInLocalStorage", value);
}, [value]);
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type="text" onChange={onChange} />
<p>{value}</p>
</div>
);
};
這段代碼演示了useEffect鉤子的用法,每次狀態中的輸入框的值改變時,該鉤子就會被執行。當提供給useEffect鉤子的函數被執行時,它會利用最新的值更新local storage中的值。此外,函數組件的初始狀態也可以使用useState鉤子從local storage中讀取。
最後一點,我們可以將講個鉤子提取出來,封裝成一個自定義鉤子,這樣可以保證組件狀態永遠和local storage同步。它在最後會返回一個值和setter函數,供函數組件使用:
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = React.useState(
localStorage.getItem(localStorageKey) || "",
);
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
const App = () => {
const [value, setValue] = useStateWithLocalStorage(
"myValueInLocalStorage",
);
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type="text" onChange={onChange} />
<p>{value}</p>
</div>
);
};
由於這段代碼是從函數組件中提取出來的,它可以用於任何其他組件,以實現業務邏輯的復用。它與Mixin、高階組件和Render Prop組件一樣都是高級設計模式。但是需要指出的是,React的函數組件也可以用高階組件和Render Prop組件來增強。
React函數組件、鉤子和類組件是目前編寫現代React應用程序的標準。但是,我堅信以後函數組件和鉤子將取代類組件。屆時,類組件也許只會出現在舊的應用程序和教程中,就像今天的 createClass組件和Mixin一樣。高階組件和Render Prop組件也同理,它們也會被鉤子取代。
寫在最後
所有React的組件在Pros的用法方面的理念都是一樣的,都是將信息沿著組件樹向下傳遞。但是,類組件和函數組件對於狀態和副作用的用法是不同的,還有生命周期方法和鉤子。
這篇文章介紹了所有不同種類的React組件及其用法,以及它們在歷史中的位置。最後總結一下,現在使用類組件、函數組件和鉤子、高階組件和Render Prop組件等高級概念是完全沒問題的。但是也應當了解到,舊的React應用程序和教程也會使用一些只有以前才使用的舊組件和設計模式。
原文:https://www.robinwieruch.de/react-component-types/
本文為 CSDN 翻譯,如需轉載,請註明來源出處。作者獨立觀點,不代表 CSDN 立場。


※每秒幾十萬的大規模網路爬蟲是如何煉成的?
※小米「祭出」 AIoT 神器!| 技術頭條
TAG:CSDN |