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