摘要:我們可以通過剛剛高階函數(shù)的思想來創(chuàng)建一個中間組件,也就是我們說的高階組件。僅傳遞組件所需要的屬性。在受控組件中,表單數(shù)據(jù)由組件負責(zé)處理。作為頂層組件接收一個名為的,可以接收任意需要被放入中的字符串,數(shù)字,甚至是函數(shù)。
React組件設(shè)計 組件分類 展示組件和容器組件
展示組件 | 容器組件 | |
---|---|---|
關(guān)注事物的展示 | 關(guān)注事物如何工作 | |
可能包含展示和容器組件,并且一般會有DOM標(biāo)簽和css樣式 | 可能包含展示和容器組件,并且不會有DOM標(biāo)簽和css樣式 | |
常常允許通過this.props.children傳遞 | 提供數(shù)據(jù)和行為給容器組件或者展示組件 | |
對第三方?jīng)]有任何依賴,比如store 或者 flux action | 調(diào)用flux action 并且提供他們的回調(diào)給展示組件 | |
不要指定數(shù)據(jù)如何加載和變化 | 作為數(shù)據(jù)源,通常采用較高階的組件,而不是自己寫,比如React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create() | |
僅通過屬性獲取數(shù)據(jù)和回調(diào) | null | |
很少有自己的狀態(tài),即使有,也是自己的UI狀態(tài) | null | |
除非他們需要的自己的狀態(tài),生命周期,或性能優(yōu)化才會被寫為功能組件 | null |
下面是一個可能會經(jīng)常寫的組件,評論列表組件,數(shù)據(jù)交互和展示都放到了一個組件里面。
// CommentList.js class CommentList extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: "json", success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return
我們對上面的組件進行拆分,把他拆分成容器組件 CommentListContainer.js 和展示組件 CommentList。
// CommentListContainer.js class CommentListContainer extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: "json", success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return; } } // CommentList.js class CommentList extends React.Component { constructor(props) { super(props); } render() { return
優(yōu)勢:
展示和容器更好的分離,更好的理解應(yīng)用程序和UI
重用性高,展示組件可以用于多個不同的state數(shù)據(jù)源
展示組件就是你的調(diào)色板,可以把他們放到多帶帶的頁面,在不影響應(yīng)用程序的情況下,讓設(shè)計師調(diào)整UI
迫使你分離標(biāo)簽,達到更高的可用性
有狀態(tài)組件和無狀態(tài)組件下面是一個最簡單的無狀態(tài)組件的例子:
function HelloComponent(props, /* context */) { returnHello {props.name}} ReactDOM.render(, mountNode)
可以看到,原本需要寫“類”定義(React.createClass 或者 class YourComponent extends React.Component)來創(chuàng)建自己組件的定義(有狀態(tài)組件),現(xiàn)在被精簡成了只寫一個 render 函數(shù)。更值得一提的是,由于僅僅是一個無狀態(tài)函數(shù),React 在渲染的時候也省掉了將“組件類” 實例化的過程。
結(jié)合 ES6 的解構(gòu)賦值,可以讓代碼更精簡。例如下面這個 Input 組件:
function Input({ label, name, value, ...props }, { defaultTheme }) { const { theme, autoFocus, ...rootProps } = props return (
無狀態(tài)組件不像上述兩種方法在調(diào)用時會創(chuàng)建新實例,它創(chuàng)建時始終保持了一個實例,避免了不必要的檢查和內(nèi)存分配,做到了內(nèi)部優(yōu)化。
無狀態(tài)組件不支持 "ref"高階組件
高階組件通過函數(shù)和閉包,改變已有組件的行為,本質(zhì)上就是 Decorator 模式在 React 的一種實現(xiàn)。
當(dāng)寫著寫著無狀態(tài)組件的時候,有一天忽然發(fā)現(xiàn)需要狀態(tài)處理了,那么無需徹底返工:)
往往我們需要狀態(tài)的時候,這個需求是可以重用的。
高階組件加無狀態(tài)組件,則大大增強了整個代碼的可測試性和可維護性。同時不斷“誘使”我們寫出組合性更好的代碼。
高階函數(shù)function welcome() { let username = localStorage.getItem("username"); console.log("welcome " + username); } function goodbey() { let username = localStorage.getItem("username"); console.log("goodbey " + username); } welcome(); goodbey();
我們發(fā)現(xiàn)兩個函數(shù)有一句代碼是一樣的,這叫冗余唉。(平時可能會有一大段代碼的冗余)。
下面我們要寫一個中間函數(shù),讀取username,他來負責(zé)把username傳遞給兩個函數(shù)。
function welcome(username) { console.log("welcome " + username); } function goodbey(username) { console.log("goodbey " + username); } function wrapWithUsername(wrappedFunc) { let newFunc = () => { let username = localStorage.getItem("username"); wrappedFunc(username); }; return newFunc; } welcome = wrapWithUsername(welcome); goodbey = wrapWithUsername(goodbey); welcome(); goodbey();
好了,我們里面的 wrapWithUsername 函數(shù)就是一個“高階函數(shù)”。
他做了什么?他幫我們處理了 username,傳遞給目標(biāo)函數(shù)。我們調(diào)用最終的函數(shù) welcome的時候,根本不用關(guān)心 username是怎么來的。
下面是兩個冗余的組件。
import React, {Component} from "react" class Welcome extends Component { constructor(props) { super(props); this.state = { username: "" } } componentWillMount() { let username = localStorage.getItem("username"); this.setState({ username: username }) } render() { return (welcome {this.state.username}) } } export default Welcome;
import React, {Component} from "react" class Goodbye extends Component { constructor(props) { super(props); this.state = { username: "" } } componentWillMount() { let username = localStorage.getItem("username"); this.setState({ username: username }) } render() { return (goodbye {this.state.username}) } } export default Goodbye;
我們可以通過剛剛高階函數(shù)的思想來創(chuàng)建一個中間組件,也就是我們說的高階組件。
import React, {Component} from "react" export default (WrappedComponent) => { class NewComponent extends Component { constructor() { super(); this.state = { username: "" } } componentWillMount() { let username = localStorage.getItem("username"); this.setState({ username: username }) } render() { return} } return NewComponent }
import React, {Component} from "react"; import wrapWithUsername from "wrapWithUsername"; class Welcome extends Component { render() { return (welcome {this.props.username}) } } Welcome = wrapWithUsername(Welcome); export default Welcome;
import React, {Component} from "react"; import wrapWithUsername from "wrapWithUsername"; class Goodbye extends Component { render() { return (goodbye {this.props.username}) } } Goodbye = wrapWithUsername(Goodbye); export default Goodbye;
看到?jīng)]有,高階組件就是把 username 通過 props 傳遞給目標(biāo)組件了。目標(biāo)組件只管從 props里面拿來用就好了。
為了代碼的復(fù)用性,我們應(yīng)該盡量減少代碼的冗余。
提取共享的state,如果有兩個組件都需要加載同樣的數(shù)據(jù),那么他們會有相同的 componentDidMount 函數(shù)。
找出重復(fù)的代碼,每個組件中constructor 和 componentDidMount都干著同樣的事情,另外,在數(shù)據(jù)拉取時都會顯示Loading... 文案,那么我們應(yīng)該思考如何使用高階組件來提取這些方法。
遷移重復(fù)的代碼到高階組件
包裹組件,并且使用props替換state
盡可能地簡化
組件開發(fā)基本思想 單功能原則使用react時,組件或容器的代碼在根本上必須只負責(zé)一塊UI功能。
讓組件保持簡單如果組件根本不需要狀態(tài),那么就使用函數(shù)定義的無狀態(tài)組件。
從性能上來說,函數(shù)定義的無狀態(tài)組件 > ES6 class 定義的組件 > 通過 React.createClass() 定義的組件。
僅傳遞組件所需要的屬性。只有當(dāng)屬性列表太長時,才使用{...this.props}進行傳遞。
如果組件里面有太多的判斷邏輯(if-else語句)通常意味著這個組件需要被拆分成更細的組件或模塊。
使用明確的命名能夠讓開發(fā)者明白它的功能,有助于組件復(fù)用。
基本準(zhǔn)則在shouldComponentUpdate中避免不必要的檢查.
盡量使用不可變數(shù)據(jù)類型(Immutable).
編寫針對產(chǎn)品環(huán)境的打包配置(Production Build).
通過Chrome Timeline來記錄組件所耗費的資源.
在componentWillMount或者componentDidMount里面通過setTimeOut或者requestAnimationFram來延遲執(zhí)行那些需要大量計算的任務(wù).
組件開發(fā)技巧 form表單里的受控組件和不受控組件 受控組件在大多數(shù)情況下,我們推薦使用受控組件來實現(xiàn)表單。在受控組件中,表單數(shù)據(jù)由 React 組件負責(zé)處理。下面是一個典型的受控組建。
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ""}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert("A name was submitted: " + this.state.value); event.preventDefault(); } render() { return (); } }
設(shè)置表單元素的value屬性之后,其顯示值將由this.state.value決定,以滿足React狀態(tài)的同一數(shù)據(jù)理念。每次鍵盤敲擊之后會執(zhí)行handleChange方法以更新React狀態(tài),顯示值也將隨著用戶的輸入改變。
對于受控組件來說,每一次 state(狀態(tài))變化都會伴有相關(guān)聯(lián)的處理函數(shù)。這使得可以直接修改或驗證用戶的輸入和提交表單。
不受控組件因為不受控組件的數(shù)據(jù)來源是 DOM 元素,當(dāng)使用不受控組件時很容易實現(xiàn) React 代碼與非 React 代碼的集成。如果你希望的是快速開發(fā)、不要求代碼質(zhì)量,不受控組件可以一定程度上減少代碼量。否則。你應(yīng)該使用受控組件。
一般情況下不受控組件我們使用ref來獲取DOM元素進行操作。
class NameForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(event) { alert("A name was submitted: " + this.input.value); event.preventDefault(); } render() { return (); } } 組件條件判斷 三元函數(shù)組件判斷渲染
const sampleComponent = () => { return isTrue ?使用&&表達式替換不必要的三元函數(shù)True!
:false!
};
const sampleComponent = () => { return isTrue ?True!
:};
const sampleComponent = () => { return isTrue &&True!
};
需要注意的是如果isTrue 為 0 ,其實會轉(zhuǎn)換成 false,但是在頁面中顯示的時候,&&還是會返回0顯示到頁面中。
多重嵌套判斷// 問題代碼 const sampleComponent = () => { return ({flag && flag2 && !flag3 ? flag4 ?) };Blah
: flag5 ?Meh
:Herp
:Derp
}
解決方案:
最佳方案: 將邏輯移到子組件內(nèi)部
使用IIFE(Immediately-Invoked Function Expression 立即執(zhí)行函數(shù))
滿足條件的時候使用return強制跳出函數(shù)
const sampleComponent = () => { const basicCondition = flag && flag2 && !flag3; if (!basicCondition) returnsetState異步性Derp
; if (flag4) returnBlah
; if (flag5) returnMeh
; returnHerp
}
在某些情況下,React框架出于性能優(yōu)化考慮,可能會將多次state更新合并成一次更新。正因為如此,setState實際上是一個異步的函數(shù)。 如果在調(diào)用setState()函數(shù)之后嘗試去訪問this.state,你得到的可能還是setState()函數(shù)執(zhí)行之前的結(jié)果。
但是,有一些行為也會阻止React框架本身對于多次state更新的合并,從而讓state的更新變得同步化。 比如: eventListeners, Ajax, setTimeout 等等。
React框架之所以在選擇在調(diào)用setState函數(shù)之后立即更新state而不是采用框架默認的方式,即合并多次state更新為一次更新,是因為這些函數(shù)調(diào)用(fetch,setTimeout等瀏覽器層面的API調(diào)用)并不處于React框架的上下文中,React沒有辦法對其進行控制。React在此時采用的策略就是及時更新,確保在這些函數(shù)執(zhí)行之后的其他代碼能拿到正確的數(shù)據(jù)(即更新過的state)。
解決setState函數(shù)異步的辦法?根據(jù)React官方文檔,setState函數(shù)實際上接收兩個參數(shù),其中第二個參數(shù)類型是一個函數(shù),作為setState函數(shù)執(zhí)行后的回調(diào)。通過傳入回調(diào)函數(shù)的方式,React可以保證傳入的回調(diào)函數(shù)一定是在setState成功更新this.state之后再執(zhí)行。
this.setState({count: 1}, () => { console.log(this.state.count); // 1 })React源碼中setState的實現(xiàn)
ReactComponent.prototype.setState = function(partialState, callback) { invariant( typeof partialState === "object" || typeof partialState === "function" || partialState == null, "setState(...): takes an object of state variables to update or a " + "function which returns an object of state variables." ); this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, "setState"); } };
updater的這兩個方法,和React底層的Virtual Dom(虛擬DOM樹)的diff算法有緊密的關(guān)系,所以真正決定同步還是異步的其實是Virtual DOM的diff算法。
依賴注入在React中,想做依賴注入(Dependency Injection)其實相當(dāng)簡單??梢酝ㄟ^props來進行傳遞。但是,如果組件數(shù)量很多,并且組件嵌套層次很深的話,這種方式就不太合適。
高階組件// inject.jsx var title = "React Dependency Injection"; export default function inject(Component) { return class Injector extends React.Component { render() { return () } }; }
// Title.jsx export default function Title(props) { return{ props.title }
; }
// Header.jsx import inject from "./inject.jsx"; import Title from "./Title.jsx"; var EnhancedTitle = inject(Title); export default function Header() { return (context); }
React v16.3.0 之前的 Context:
var context = { title: "React in patterns" }; class App extends React.Component { getChildContext() { return context; } // ... } App.childContextTypes = { title: PropTypes.string };
class Inject extends React.Component { render() { var title = this.context.title; // ... } } Inject.contextTypes = { title: PropTypes.string };
之前的 Context 作為一個實驗性質(zhì)的 API,直到 React v16.3.0 版本前都一直不被官方所提倡去使用,其主要原因就是因為在子組件中使用 Context 會破壞 React 應(yīng)用的分型架構(gòu)。
這里的分形架構(gòu)指的是從理想的 React 應(yīng)用的根組件樹中抽取的任意一部分都仍是一個可以直接運行的子組件樹。在這個子組件樹之上再包一層,就可以將它無縫地移植到任意一個其他的根組件樹中。
但如果根組件樹中有任意一個組件使用了支持透傳的 Context API,那么如果把包含了這個組件的子組件樹多帶帶拿出來,因為缺少了提供 Context 值的根組件樹,這時的這個子組件樹是無法直接運行的。
并且他有一個致命缺陷:任何一個中間傳遞的組件shouldComponentUpdate 函數(shù)返回false,組件都不會得到更新。
新的Context Api
新的Context Api 采用聲明式的寫法,并且可以透過shouldComponentUpdate 函數(shù)返回false的組件繼續(xù)向下傳播,以保證目標(biāo)組件一定可以接收到頂層組件 Context 值的更新,一舉解決了現(xiàn)有 Context API 的兩大弊端,也終于成為了 React 中的第一級(first-class) API。
新的 Context API 分為三個組成部分:
React.createContext 用于初始化一個 Context。
XXXContext.Provider作為頂層組件接收一個名為 value的 prop,可以接收任意需要被放入 Context 中的字符串,數(shù)字,甚至是函數(shù)。
XXXContext.Consumer作為目標(biāo)組件可以出現(xiàn)在組件樹的任意位置(在 Provider 之后),接收 children prop,這里的 children 必須是一個函數(shù)(context => ())用來接收從頂層傳來的 Context。
const ThemeContext = React.createContext("light"); class App extends React.Component { render() { return (事件處理中的this指向問題); } } function Toolbar(props) { return ( ); } function ThemedButton(props) { return ({theme => } ); }
class Switcher extends React.Component { constructor(props) { super(props); this.state = { name: "React in patterns" }; } render() { return ( ); } _handleButtonClick() { console.log(`Button is clicked inside ${ this.state.name }`); // 將導(dǎo)致 // Uncaught TypeError: Cannot read property "state" of null } }
我們可以通過下面三種方式簡單實現(xiàn)this指向的綁定:
在constructor 中事先綁定 this._buttonClick = this._handleButtonClick.bind(this);
調(diào)用時使用箭頭函數(shù)
ES7中的綁定操作符
給setState傳入回調(diào)函數(shù)setState() 不僅能接受一個對象,還能接受一個函數(shù)作為參數(shù)呢,該函數(shù)接受該組件前一刻的 state 以及當(dāng)前的 props 作為參數(shù),計算和返回下一刻的 state。
// assuming this.state.count === 0 this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); // this.state.count === 1, not 3 this.setState((prevState, props) => ({ count: prevState.count + props.increment }));
// Passing object this.setState({ expanded: !this.state.expanded }); // Passing function this.setState(prevState => ({ expanded: !prevState.expanded }));組件切換技巧
import HomePage from "./HomePage.jsx"; import AboutPage from "./AboutPage.jsx"; import UserPage from "./UserPage.jsx"; import FourOhFourPage from "./FourOhFourPage.jsx"; const PAGES = { home: HomePage, about: AboutPage, user: UserPage }; const Page = (props) => { const Handler = PAGES[props.page] || FourOhFourPage; returnReact style 組件分類};
基礎(chǔ)組件, 布局組件, 排版組件
給無狀態(tài)的純UI組件應(yīng)用樣式請保持樣式遠離那些離不開state的組件. 比如路由, 視圖, 容器, 表單, 布局等等不應(yīng)該有任何的樣式或者css class出現(xiàn)在組件上. 相反, 這些復(fù)雜的業(yè)務(wù)組件應(yīng)該有一些帶有基本功能的無狀態(tài)UI組件組成.
class SampleComponent extends Component { render() { return () } } // 表達組件(帶樣式) const Button = ({ ...props }) => { const sx = { fontFamily: "inherit", fontSize: "inherit", fontWeight: "bold", textDecoration: "none", display: "inline-block", margin: 0, paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16, border: 0, color: "white", backgroundColor: "blue", WebkitAppearance: "none", MozAppearance: "none" } return (
摘要:前端每周清單第期微服務(wù)實踐,與,組件技巧,攻防作者王下邀月熊編輯徐川前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點分為新聞熱點開發(fā)教程工程實踐深度閱讀開源項目巔峰人生等欄目。 前端每周清單第 26 期:Node.js 微服務(wù)實踐,Vue.js 與 GraphQL,Angular 組件技巧,HeadlessChrome 攻防 作者:王下邀月熊 編輯:徐川...
摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個技巧,并利用實例說明了源碼學(xué)習(xí)過程中可以學(xué)到許多周邊知識,都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...
摘要:需要注意的是,同樣的行為也適用于。這意味著我們必須重新綁定每個事件。組件的由調(diào)用它的父組件提供,這意味著所有事件都應(yīng)該與父組件相關(guān)聯(lián)。 原文鏈接:Vue.js — Considerations and Tricks showImg(https://segmentfault.com/img/bVbqHOd?w=1600&h=1599); Vue.js 是一個很棒的框架。然而,當(dāng)你開始構(gòu)建...
摘要:特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進步。 特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會及時更新,平時業(yè)務(wù)工作時也會不定期更...
閱讀 2891·2021-08-20 09:37
閱讀 1617·2019-08-30 12:47
閱讀 1101·2019-08-29 13:27
閱讀 1693·2019-08-28 18:02
閱讀 758·2019-08-23 18:15
閱讀 3095·2019-08-23 16:51
閱讀 939·2019-08-23 14:13
閱讀 2156·2019-08-23 13:05