摘要:返回元素的是將新的與原始元素的淺層合并后的結(jié)果。生命周期方法要如何對應(yīng)到函數(shù)組件不需要構(gòu)造函數(shù)。除此之外,可以認(rèn)為的設(shè)計在某些方面更加高效避免了需要的額外開支,像是創(chuàng)建類實(shí)例和在構(gòu)造函數(shù)中綁定事件處理器的成本。
React系列
React系列 --- 簡單模擬語法(一)
React系列 --- Jsx, 合成事件與Refs(二)
React系列 --- virtualdom diff算法實(shí)現(xiàn)分析(三)
React系列 --- 從Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement與Component部分源碼解析(五)
React系列 --- 從使用React了解Css的各種使用方案(六)
這是React初期提供的一種組合方案,通過引入一個公用組件,然后可以應(yīng)用公用組件的一些生命周期操作或者定義方法,達(dá)到抽離公用代碼提供不同模塊使用的目的.
曾經(jīng)的官方文檔demo如下
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.map(clearInterval); }, }; var TickTock = React.createClass({ mixins: [SetIntervalMixin], // Use the mixin getInitialState: function() { return { seconds: 0 }; }, componentDidMount: function() { this.setInterval(this.tick, 1000); // Call a method on the mixin }, tick: function() { this.setState({ seconds: this.state.seconds + 1 }); }, render: function() { returnReact has been running for {this.state.seconds} seconds.
; }, }); React.render(, document.getElementById("example"));
但是Mixins只能應(yīng)用在createClass的創(chuàng)建方式,在后來的class寫法中已經(jīng)被廢棄了.原因在于:
mixin引入了隱式依賴關(guān)系
不同mixins之間可能會有先后順序甚至代碼沖突覆蓋的問題
mixin代碼會導(dǎo)致滾雪球式的復(fù)雜性
詳細(xì)介紹mixin危害性文章可直接查閱Mixins Considered Harmful
高階組件(Higher-order component)HOC是一種React的進(jìn)階使用方法,大概原理就是接收一個組件然后返回一個新的繼承組件,繼承方式分兩種
屬性代理(Props Proxy)最基本的實(shí)現(xiàn)方式
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { return} } }
從代碼可以看出屬性代理方式其實(shí)就是接受一個 WrappedComponent 組件作為參數(shù)傳入,并返回一個繼承了 React.Component 組件的類,且在該類的 render() 方法中返回被傳入的 WrappedComponent 組件
抽離state && 操作propsfunction PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { constructor(props) { super(props); this.state = { name: "PropsProxyHOC", }; } logName() { console.log(this.name); } render() { const newProps = { name: this.state.name, logName: this.logName, }; return; } }; } class Main extends Component { componentDidMount() { this.props.logName(); } render() { return PropsProxyHOC; } } export default PropsProxyHOC(Main);
demo代碼可以參考這里
有種常見的情況是用來做雙向綁定
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { constructor(props) { super(props); this.state = { fields: {} }; } getField(fieldName) { const _s = this.state; if (!_s.fields[fieldName]) { _s.fields[fieldName] = { value: "", onChange: event => { this.state.fields[fieldName].value = event.target.value; // 強(qiáng)行觸發(fā)render this.forceUpdate(); console.log(this.state); }, }; } return { value: _s.fields[fieldName].value, onChange: _s.fields[fieldName].onChange, }; } render() { const newProps = { fields: this.getField.bind(this), }; return; } }; } // 被獲取ref實(shí)例組件 class Main extends Component { render() { return ; } } export default PropsProxyHOC(Main);
demo代碼可以參考這里
獲取被繼承refs實(shí)例因?yàn)檫@是一個被HOC包裝過的新組件,所以想要在HOC里面獲取新組件的ref需要用些特殊方式,但是不管哪種,都需要在組件掛載之后才能獲取到.并且不能在無狀態(tài)組件(函數(shù)類型組件)上使用 ref 屬性,因?yàn)闊o狀態(tài)組件沒有實(shí)例。
通過父元素傳遞方法獲取function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { const newProps = {}; // 監(jiān)聽到有對應(yīng)方法才生成props實(shí)例 typeof this.props.getInstance === "function" && (newProps.ref = this.props.getInstance); return; } }; } // 被獲取ref實(shí)例組件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { componentWillMount() { console.log("componentWillMount: ", this.wrappedInstance); } componentDidMount() { console.log("componentDidMount: ", this.wrappedInstance); } // 提供給高階組件調(diào)用生成實(shí)例 getInstance(ref) { this.wrappedInstance = ref; } render() { return; } } export default ParentComponent;
demo代碼可以參考這里
通過高階組件當(dāng)中間層相比較上一方式,需要在高階組件提供設(shè)置賦值函數(shù),并且需要一個props屬性做標(biāo)記
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { // 返回ref實(shí)例 getWrappedInstance = () => { if (this.props.withRef) { return this.wrappedInstance; } }; //設(shè)置ref實(shí)例 setWrappedInstance = ref => { this.wrappedInstance = ref; }; render() { const newProps = {}; // 監(jiān)聽到有對應(yīng)方法才賦值props實(shí)例 this.props.withRef && (newProps.ref = this.setWrappedInstance); return; } }; } // 被獲取ref實(shí)例組件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { componentWillMount() { console.log("componentWillMount: ", this.refs.child); } componentDidMount() { console.log("componentDidMount: ", this.refs.child.getWrappedInstance()); } render() { return; } } export default ParentComponent;
demo代碼可以參考這里
forwardRefReact.forwardRef 會創(chuàng)建一個React組件,這個組件能夠?qū)⑵浣邮艿?ref 屬性轉(zhuǎn)發(fā)到其組件樹下的另一個組件中。這種技術(shù)并不常見,但在以下兩種場景中特別有用:
轉(zhuǎn)發(fā) refs 到 DOM 組件
在高階組件中轉(zhuǎn)發(fā) refs
const FancyButton = React.forwardRef((props, ref) => ( )); // You can now get a ref directly to the DOM button: const ref = React.createRef();Click me! ;
以下是對上述示例發(fā)生情況的逐步解釋:
我們通過調(diào)用 React.createRef 創(chuàng)建了一個 React ref 并將其賦值給 ref 變量。
我們通過指定 ref 為 JSX 屬性,將其向下傳遞給
React 傳遞 ref 給 fowardRef 內(nèi)函數(shù) (props, ref) => ...,作為其第二個參數(shù)。
我們向下轉(zhuǎn)發(fā)該 ref 參數(shù)到 ,將其指定為 JSX 屬性。
當(dāng) ref 掛載完成,ref.current 將指向 DOM 節(jié)點(diǎn)。
劫持渲染最簡單的例子莫過于loading組件了
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { return this.props.isLoading ?Loading...:; } }; } // 被獲取ref實(shí)例組件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { constructor() { super(); this.state = { isLoading: true, }; } render() { setTimeout(() => this.setState({ isLoading: false }), 2000); return; } } export default ParentComponent;
當(dāng)然也能用于布局上嵌套在其他元素輸出
demo代碼可以參考這里
最簡單的demo代碼
function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { render() { return super.render(); } }; }
在這里WrappedComponent成了被繼承的那一方,從而可以在高階組件中獲取到傳遞組件的所有相關(guān)實(shí)例
獲取繼承組件實(shí)例function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { componentDidMount() { console.log("componentDidMount: ", this); } render() { return super.render(); } }; } // 被獲取ref實(shí)例組件 class Main extends Component { constructor() { super(); this.state = { name: "WrappedComponent", }; } render() { returnMain; } } export default InheritanceInversionHOC(Main);
demo代碼可以參考這里
修改props和劫持渲染再講解demo之前先科普React的一個方法
React.cloneElement( element, [props], [...children] )
以 element 元素為樣板克隆并返回新的 React 元素。返回元素的 props 是將新的 props 與原始元素的 props 淺層合并后的結(jié)果。新的子元素將取代現(xiàn)有的子元素,而來自原始元素的 key 和 ref 將被保留。
React.cloneElement() 幾乎等同于:
{children}
但是,這也保留了組件的 ref。這意味著當(dāng)通過 ref 獲取子節(jié)點(diǎn)時,你將不會意外地從你祖先節(jié)點(diǎn)上竊取它。相同的 ref 將添加到克隆后的新元素中。
相比屬性繼承來說,反向繼承修改props會比較復(fù)雜一點(diǎn)
function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { constructor() { super(); this.state = { a: "b", }; } componentDidMount() { console.log("componentDidMount: ", this); } render() { const wrapperTree = super.render(); const newProps = { name: "NewComponent", }; const newTree = React.cloneElement(wrapperTree, newProps, wrapperTree.props.children); console.log("newTree: ", newTree); return newTree; } }; } // 被獲取ref實(shí)例組件 class Main extends Component { render() { returnMain; } } export default InheritanceInversionHOC(Main);
demo代碼可以參考這里
為什么需要用到cloneElement方法?因?yàn)閞ender函數(shù)內(nèi)實(shí)際上是調(diào)用React.creatElement產(chǎn)生的React元素,盡管我們可以拿到這個方法但是無法修改它.可以用getOwnPropertyDescriptors查看它的配置項(xiàng)
demo代碼可以參考這里
本質(zhì)上,useRef 就像是可以在其 .current 屬性中保存一個可變值的“盒子”。
你應(yīng)該熟悉 ref 這一種訪問 DOM 的主要方式。如果你將 ref 對象以
不要在循環(huán),條件,或者內(nèi)嵌函數(shù)中調(diào)用.這都是為了保證你的代碼在每次組件render的時候會按照相同的順序執(zhí)行HOOKS,而這也是能夠讓React在多個useState和useEffect執(zhí)行中正確保存數(shù)據(jù)的原因
只在React函數(shù)調(diào)用HOOKSReact函數(shù)組件調(diào)用
從自定義HOOKS中調(diào)用
可以確保你源碼中組件的所有有狀態(tài)邏輯都是清晰可見的.
自定義HOOKS我們可以將相關(guān)邏輯抽取出來
function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
我必須以“use”開頭為自定義鉤子命名嗎? 這項(xiàng)公約非常重要。如果沒有它,我們就不能自動檢查鉤子是否違反了規(guī)則,因?yàn)槲覀儫o法判斷某個函數(shù)是否包含對鉤子的調(diào)用。
使用相同鉤子的兩個組件是否共享狀態(tài)? 不。自定義鉤子是一種重用有狀態(tài)邏輯的機(jī)制(例如設(shè)置訂閱并記住當(dāng)前值),但是每次使用自定義鉤子時,其中的所有狀態(tài)和效果都是完全隔離的。
自定義鉤子如何獲得隔離狀態(tài)? 對鉤子的每個調(diào)用都處于隔離狀態(tài)。從React的角度來看,我們的組件只調(diào)用useState和useEffect。
問題 Hook 會替代 render props 和高階組件嗎?通常,render props 和高階組件只渲染一個子節(jié)點(diǎn)。我們認(rèn)為讓 Hook 來服務(wù)這個使用場景更加簡單。這兩種模式仍有用武之地,(例如,一個虛擬滾動條組件或許會有一個 renderItem 屬性,或是一個可見的容器組件或許會有它自己的 DOM 結(jié)構(gòu))。但在大部分場景下,Hook 足夠了,并且能夠幫助減少嵌套。
生命周期方法要如何對應(yīng)到 Hook?constructor:函數(shù)組件不需要構(gòu)造函數(shù)。你可以通過調(diào)用 useState 來初始化 state。如果計算的代價比較昂貴,你可以傳一個函數(shù)給 useState。
getDerivedStateFromProps:改為在渲染時安排一次更新。
shouldComponentUpdate:詳見 React.memo.
render:這是函數(shù)組件體本身。
componentDidMount, componentDidUpdate, componentWillUnmount:useEffect Hook 可以表達(dá)所有這些的組合。
componentDidCatch and getDerivedStateFromError:目前還沒有這些方法的 Hook 等價寫法,但很快會加上。
我可以只在更新時運(yùn)行 effect 嗎?這是個比較罕見的使用場景。如果你需要的話,你可以 使用一個可變的 ref 手動存儲一個布爾值來表示是首次渲染還是后續(xù)渲染,然后在你的 effect 中檢查這個標(biāo)識。
如何獲取上一輪的 props 或 state?目前,你可以通過ref來手動實(shí)現(xiàn):
function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return (有類似 forceUpdate 的東西嗎?Now: {count}, before: {prevCount}
); } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
如果前后兩次的值相同,useState 和 useReducer Hook 都會放棄更新。原地修改 state 并調(diào)用 setState 不會引起重新渲染。
通常,你不應(yīng)該在 React 中修改本地 state。然而,作為一條出路,你可以用一個增長的計數(shù)器來在 state 沒變的時候依然強(qiáng)制一次重新渲染:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0); function handleClick() { forceUpdate(); }我該如何測量 DOM 節(jié)點(diǎn)?
要想測量一個 DOM 節(jié)點(diǎn)的位置或是尺寸,你可以使用 callback ref。每當(dāng) ref 被附加到另一個節(jié)點(diǎn),React 就會調(diào)用 callback。
function MeasureExample() { const [rect, ref] = useClientRect(); return (); } function useClientRect() { const [rect, setRect] = useState(null); const ref = useCallback(node => { if (node !== null) { setRect(node.getBoundingClientRect()); } }, []); return [rect, ref]; }Hello, world
{rect !== null &&The above header is {Math.round(rect.height)}px tall
}
demo代碼可以參考這里
使用 callback ref 可以確保 即便子組件延遲顯示被測量的節(jié)點(diǎn) (比如為了響應(yīng)一次點(diǎn)擊),我們依然能夠在父組件接收到相關(guān)的信息,以便更新測量結(jié)果。
注意到我們傳遞了 [] 作為 useCallback 的依賴列表。這確保了 ref callback 不會在再次渲染時改變,因此 React 不會在非必要的時候調(diào)用它。
我該如何實(shí)現(xiàn) shouldComponentUpdate?你可以用 React.memo 包裹一個組件來對它的 props 進(jìn)行淺比較:
const Button = React.memo((props) => { // 你的組件 });
React.memo 等效于 PureComponent,但它只比較 props。(你也可以通過第二個參數(shù)指定一個自定義的比較函數(shù)來比較新舊 props。如果函數(shù)返回 true,就會跳過更新。)
React.memo 不比較 state,因?yàn)闆]有單一的 state 對象可供比較。但你也可以讓子節(jié)點(diǎn)變?yōu)榧兘M件,或者 用useMemo優(yōu)化每一個具體的子節(jié)點(diǎn)。
如何惰性創(chuàng)建昂貴的對象?第一個常見的使用場景是當(dāng)創(chuàng)建初始 state 很昂貴時,為避免重新創(chuàng)建被忽略的初始 state,我們可以傳一個函數(shù)給 useState,React 只會在首次渲染時調(diào)用這個函數(shù)
function Table(props) { // createRows() 只會被調(diào)用一次 const [rows, setRows] = useState(() => createRows(props.count)); // ... }
你或許也會偶爾想要避免重新創(chuàng)建 useRef() 的初始值。useRef 不會像 useState 那樣接受一個特殊的函數(shù)重載。相反,你可以編寫你自己的函數(shù)來創(chuàng)建并將其設(shè)為惰性的:
function Image(props) { const ref = useRef(null); // IntersectionObserver 只會被惰性創(chuàng)建一次 function getObserver() { let observer = ref.current; if (observer !== null) { return observer; } let newObserver = new IntersectionObserver(onIntersect); ref.current = newObserver; return newObserver; } // 當(dāng)你需要時,調(diào)用 getObserver() // ... }Hook 會因?yàn)樵阡秩緯r創(chuàng)建函數(shù)而變慢嗎?
不會。在現(xiàn)代瀏覽器中,閉包和類的原始性能只有在極端場景下才會有明顯的差別。
除此之外,可以認(rèn)為 Hook 的設(shè)計在某些方面更加高效:
Hook 避免了 class 需要的額外開支,像是創(chuàng)建類實(shí)例和在構(gòu)造函數(shù)中綁定事件處理器的成本。
符合語言習(xí)慣的代碼在使用 Hook 時不需要很深的組件樹嵌套。這個現(xiàn)象在使用高階組件、render props、和 context 的代碼庫中非常普遍。組件樹小了,React 的工作量也隨之減少。
傳統(tǒng)上認(rèn)為,在 React 中使用內(nèi)聯(lián)函數(shù)對性能的影響,與每次渲染都傳遞新的回調(diào)會如何破壞子組件的 shouldComponentUpdate 優(yōu)化有關(guān)。Hook 從三個方面解決了這個問題。
useCallback Hook 允許你在重新渲染之間保持對相同的回調(diào)引用以使得 shouldComponentUpdate 繼續(xù)工作:
useMemo Hook 使控制具體子節(jié)點(diǎn)何時更新變得更容易,減少了對純組件的需要。
最后,useReducer Hook 減少了對深層傳遞回調(diào)的需要,就如下面解釋的那樣。
如何避免向下傳遞回調(diào)?在大型的組件樹中,我們推薦的替代方案是通過 context 用 useReducer 往下傳一個 dispatch 函數(shù):
const TodosDispatch = React.createContext(null); function TodosApp() { // 提示:`dispatch` 不會在重新渲染之間變化 const [todos, dispatch] = useReducer(todosReducer); return (); }
TodosApp 內(nèi)部組件樹里的任何子節(jié)點(diǎn)都可以使用 dispatch 函數(shù)來向上傳遞 actions
function DeepChild(props) { // 如果我們想要執(zhí)行一個 action,我們可以從 context 中獲取 dispatch。 const dispatch = useContext(TodosDispatch); function handleClick() { dispatch({ type: "add", text: "hello" }); } return ; }
總而言之,從維護(hù)的角度來這樣看更加方便(不用不斷轉(zhuǎn)發(fā)回調(diào)),同時也避免了回調(diào)的問題。像這樣向下傳遞 dispatch 是處理深度更新的推薦模式。
React 是如何把對 Hook 的調(diào)用和組件聯(lián)系起來的?React 保持對當(dāng)先渲染中的組件的追蹤。多虧了 Hook 規(guī)范,我們得知 Hook 只會在 React 組件中被調(diào)用(或自定義 Hook —— 同樣只會在 React 組件中被調(diào)用)。
每個組件內(nèi)部都有一個「記憶單元格」列表。它們只不過是我們用來存儲一些數(shù)據(jù)的 JavaScript 對象。當(dāng)你用 useState() 調(diào)用一個 Hook 的時候,它會讀取當(dāng)前的單元格(或在首次渲染時將其初始化),然后把指針移動到下一個。這就是多個 useState() 調(diào)用會得到各自獨(dú)立的本地 state 的原因。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/106505.html
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實(shí)現(xiàn)分離業(yè)務(wù)邏輯代碼,實(shí)現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實(shí)現(xiàn)分離業(yè)務(wù)邏輯代碼,實(shí)現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實(shí)現(xiàn)分離業(yè)務(wù)邏輯代碼,實(shí)現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
摘要:與繼承相比,裝飾者是一種更輕便靈活的做法。它只是一種模式,這種模式是由自身的組合性質(zhì)必然產(chǎn)生的。對比原生組件增強(qiáng)的項(xiàng)可操作所有傳入的可操作組件的生命周期可操作組件的方法獲取反向繼承返回一個組件,繼承原組件,在中調(diào)用原組件的。 導(dǎo)讀 前端發(fā)展速度非常之快,頁面和組件變得越來越復(fù)雜,如何更好的實(shí)現(xiàn)狀態(tài)邏輯復(fù)用一直都是應(yīng)用程序中重要的一部分,這直接關(guān)系著應(yīng)用程序的質(zhì)量以及維護(hù)的難易程度。 本...
摘要:系列系列簡單模擬語法一系列合成事件與二系列算法實(shí)現(xiàn)分析三系列從到再到四系列與部分源碼解析五系列從使用了解的各種使用方案六前言我們先不講什么語法原理先根據(jù)效果強(qiáng)行模擬語法使用實(shí)現(xiàn)一個簡易版的第一步我們先用類 React系列 React系列 --- 簡單模擬語法(一)React系列 --- Jsx, 合成事件與Refs(二)React系列 --- virtualdom diff算法實(shí)現(xiàn)分析...
閱讀 1224·2021-11-25 09:43
閱讀 1979·2021-11-11 10:58
閱讀 1194·2021-11-08 13:18
閱讀 2692·2019-08-29 16:25
閱讀 3519·2019-08-29 12:51
閱讀 3316·2019-08-29 12:30
閱讀 756·2019-08-26 13:24
閱讀 3692·2019-08-26 10:38