国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - 編程技術(shù) - 當(dāng)我們討論 Hooks 時(shí)到底在討論什么

當(dāng)我們討論 Hooks 時(shí)到底在討論什么

2020-12-03 22:46掘金ES2049 編程技術(shù)

某日,老王問我:“你一直在「每周一瞥」搬運(yùn) hooks 的文章,你覺得 hooks 有哪些易造成內(nèi)存泄露的點(diǎn)?” 引發(fā)了我的深思(因?yàn)槲业哪X子一片空白)。我們一直在討論 hooks,到底在討論什么?

在使用 React 開發(fā)的這段時(shí)間里,我最大的感受就是 “這是 React 最好的時(shí)代,也是最壞的時(shí)代” !「好」在于 hooks 開啟了不一樣的開發(fā)模式,在思考方式上要求更關(guān)注于數(shù)據(jù)之間的依賴關(guān)系,同時(shí)書寫方式更加簡(jiǎn)便,總體上提升了開發(fā)效率;「壞」在于項(xiàng)目中經(jīng)常是類組件與函數(shù)組件共存,而類組件以類編程思想為主導(dǎo),開發(fā)過程中更關(guān)注于整個(gè)組件的渲染周期,在維護(hù)項(xiàng)目時(shí)常常需要在兩種思維模式中左右橫跳,這還不是最壞的一點(diǎn)。

某日,老王問我:“你一直在「每周一瞥」搬運(yùn) hooks 的文章,你覺得 hooks 有哪些易造成內(nèi)存泄露的點(diǎn)?” 引發(fā)了我的深思(因?yàn)槲业哪X子一片空白)。我們一直在討論 hooks,到底在討論什么?雖然社區(qū)內(nèi)關(guān)于 hooks 的討論很多,但更多的是科普 Hooks API 怎么使用,亦或是將其與類組件生命周期、redux 進(jìn)行對(duì)比,而缺少關(guān)于 hooks 最佳實(shí)踐的討論與共識(shí),我想這才是「最壞」的一點(diǎn)。今天,我們不妨討論一下 hooks 所帶來的變化以及我們?nèi)绾稳肀н@些變化。

注「每周一瞥」是團(tuán)隊(duì)內(nèi)翻譯并分享外網(wǎng)新鮮貨的一個(gè)專欄。

當(dāng)我們討論 Hooks 時(shí)到底在討論什么

React 16.8 發(fā)布以來,Hooks 深入人心,帶來最大的變化有三點(diǎn):思維模式的轉(zhuǎn)變,渲染過程中作用域的變化以及數(shù)據(jù)流的改變。

思維模式

從 React 官網(wǎng)可以了解到,Hooks 的設(shè)計(jì)動(dòng)機(jī)在于簡(jiǎn)化組件間狀態(tài)邏輯的復(fù)用,支持開發(fā)者將關(guān)聯(lián)的邏輯抽象為更小的函數(shù),并降低認(rèn)知成本,不用去理解 JS Class 中令人窒息的 this。在這樣的動(dòng)機(jī)之下,hooks 中弱化了組件生命周期的概念,強(qiáng)化了狀態(tài)與行為之間的依賴關(guān)系,這容易引導(dǎo)我們更多的關(guān)注“做什么”,而非“怎么做”[1]。

假設(shè)有這么一個(gè)場(chǎng)景:組件 Detail 中依賴父級(jí)組件傳入的 query 參數(shù)進(jìn)行數(shù)據(jù)請(qǐng)求,那么無論是基于類組件還是 Hooks,我們都需要定義一個(gè)異步請(qǐng)求方法 getData。不同的是,在類組件的開發(fā)模式中,我們要思考的更傾向于“怎么做”:在組件掛載完成時(shí)請(qǐng)求數(shù)據(jù),并在組件發(fā)生更新時(shí),比較新舊 query 值,必要時(shí)重新調(diào)用 getData 函數(shù)。

class Detail extends React.Component { 

  state = { 

    keyword: ''

  } 

 

  componentDidMount() { 

    this.getData(); 

  } 

 

  getSnapshotBeforeUpdate(prevProps, prevState) { 

    if (this.props.query !== prevProps.query) { 

      return true

    } 

    return null

  } 

 

  componentDidUpdate(prevProps, prevState, snapshot) { 

    if (snapshot) { 

      this.getData(); 

    } 

  } 

 

  async getData() { 

    // 這是一段異步請(qǐng)求數(shù)據(jù)的代碼 

    console.log(`數(shù)據(jù)請(qǐng)求了,參數(shù)為:${this.props.query}`); 

    this.setState({ 

      keyword: this.props.query 

    }) 

  } 

 

  render() { 

    return ( 

      <div> 

        <p>關(guān)鍵詞: {this.state.keyword}</p> 

      </div> 

    ); 

  } 

而在應(yīng)用了 Hooks 的函數(shù)組件中,我們思考“做什么”:不同 query 值,展示不同的數(shù)據(jù)。

function Detail({ 

  query 

}) { 

  const [keyword, setKeyword] = useState(''); 

 

  useEffect(() => { 

    const getData = async () => { 

      console.log(`數(shù)據(jù)請(qǐng)求了,參數(shù)為:${query}`); 

      setKeyword(query); 

    } 

 

    getData(); 

  }, [query]); 

 

  return ( 

    <div> 

      <p>關(guān)鍵詞: {keyword}</p> 

    </div> 

  ); 

在這種主導(dǎo)下,開發(fā)者在編碼過程中的思維模式也應(yīng)隨之改變,需要考慮數(shù)據(jù)與數(shù)據(jù)、數(shù)據(jù)與行為之間的同步關(guān)系。這種模式可以更簡(jiǎn)潔地將相關(guān)代碼組合到一起,甚至抽象成自定義 hooks,實(shí)現(xiàn)邏輯的共享,似乎有了插拔式編程的味道。

雖然 Dan Abramov 在自己的博客中提到,從生命周期的角度思考并決定何時(shí)執(zhí)行副作用是在逆勢(shì)而為[2],但是了解各個(gè) hooks 在組件渲染過程中的執(zhí)行時(shí)機(jī),有助于我們與 React 保持理解的一致性,能夠更加準(zhǔn)確地專注于“做什么”。 Donavon 以圖表形式梳理對(duì)比了 hooks 范式與生命周期范式[3],能夠幫助我們理解 hooks 在組件中的工作機(jī)制。每次組件發(fā)生更新時(shí),都會(huì)重新調(diào)用組件函數(shù),生成新的作用域,這種變化也對(duì)我們開發(fā)者提出了新的編碼要求。

當(dāng)我們討論 Hooks 時(shí)到底在討論什么

作用域

在類組件中,組件一旦實(shí)例化后,便有了自己的作用域,從創(chuàng)建到銷毀,作用域始終不變。因此,在整個(gè)組件的生命周期中,每次渲染時(shí)內(nèi)部變量始終指向同一個(gè)引用,我們可以很輕易的在每次渲染中通過 this.state 拿到最新的狀態(tài)值,也可以使用 this.xx 獲取到同一個(gè)內(nèi)部變量。

class Timer extends React.Component { 

  state = { 

    count: 0, 

    interval: null

  } 

 

  componentDidMount() { 

    const interval = setInterval(() => { 

      this.setState({ 

        count: this.state.count + 1, 

      }) 

    }, 1000); 

 

    this.setState({ 

      interval 

    }); 

  } 

 

  componentDidUnMount() { 

    if (this.state.interval) { 

      clearInterval(this.state.interval); 

    } 

  } 

 

  render() { 

    return ( 

      <div> 

        計(jì)數(shù)器為:{this.state.count

      </div> 

    ); 

  } 

Hooks 中, render 與 state 的關(guān)系更像閉包與局部變量。每次渲染時(shí),都會(huì)生成新的 state 變量,React 會(huì)向其寫入當(dāng)次渲染的狀態(tài)值,并在當(dāng)次渲染過程中保持不變。也即每次渲染互相獨(dú)立,都有自己的狀態(tài)值。同理,組件內(nèi)的函數(shù)、定時(shí)器、副作用等也是獨(dú)立的,內(nèi)部所訪問的也是當(dāng)次渲染的狀態(tài)值,因此常常會(huì)遇到定時(shí)器/訂閱器內(nèi)無法讀取到最新值的情況。

function Timer() { 

  const [count, setCount] = useState(0); 

 

  useEffect(() => { 

    const interval = setInterval(() => { 

      setCount(count + 1);    // 始終只為 1  

    }, 1000); 

 

    return () => { 

      clearInterval(interval); 

    } 

  }, []); 

 

  return ( 

    <div> 

      計(jì)數(shù)器為:{count

    </div> 

  ); 

如果我們想要拿到最新值,有兩種解決方法:一是利用 setCount 的 lambada 形式,傳入一個(gè)以上一次的狀態(tài)值為參數(shù)的函數(shù);二是借助 useRef 鉤子,在其 current 屬性中存儲(chǔ)最新的值。

function Timer() { 

  const [count, setCount] = useState(0); 

 

  useEffect(() => { 

    const interval = setInterval(() => { 

      setCount(c => c + 1); 

    }, 1000); 

 

    return () => { 

      clearInterval(interval); 

    } 

  }, []); 

 

  return ( 

    <div> 

      計(jì)數(shù)器為:{count

    </div> 

  ); 

在 hook-flow 的圖中,我們可以了解到當(dāng)父組件發(fā)生重新渲染時(shí),其所有(狀態(tài)、局部變量等)都是新的。一旦子組件依賴于父組件的某一個(gè)對(duì)象變量,那么無論對(duì)象是否發(fā)生變化,子組件拿到的都是新的對(duì)象,從而使子組件對(duì)應(yīng)的 diff 失效,依舊會(huì)重新執(zhí)行該部分邏輯。在下面的例子中,我們的副作用依賴項(xiàng)中包含了父組件傳入的對(duì)象參數(shù),每次父組件發(fā)生更新時(shí),都會(huì)觸發(fā)數(shù)據(jù)請(qǐng)求。

function Info({ 

  style, 

}) { 

  console.log('Info 發(fā)生渲染'); 

 

  useEffect(() => { 

    console.log('重新加載數(shù)據(jù)'); // 每次發(fā)生重新渲染時(shí),都會(huì)重新加載數(shù)據(jù) 

  }, [style]); 

 

  return ( 

    <p style={style}> 

      這是 Info 里的文字 

    </p> 

  ); 

 

function Page() { 

  console.log('Page 發(fā)生渲染'); 

 

  const [count, setCount] = useState(0); 

  const style = { color: 'red' }; 

 

  // 計(jì)數(shù)器 +1 時(shí),引發(fā) Page 的重新渲染,進(jìn)而引發(fā) Info 的重新渲染 

  return ( 

    <div> 

      <h4>計(jì)數(shù)值為:{count}</h4> 

      <button onClick={() => setCount(count + 1)}> +1 </button> 

      <Info style={style} /> 

    </div> 

  ); 

React Hooks 給我們提供了解決方案,useMemo 允許我們緩存?zhèn)魅氲膶?duì)象,僅當(dāng)依賴項(xiàng)發(fā)生變化時(shí),才重新計(jì)算并更新相應(yīng)的對(duì)象。

function Page() { 

  console.log('Page 發(fā)生渲染'); 

 

  const [color] = useState('red'); 

  const [count, setCount] = useState(0); 

  const style = useMemo(() => ({ color }), [color]); // 只有 color 發(fā)生實(shí)質(zhì)性改變時(shí),style 才會(huì)變化 

 

  // 計(jì)數(shù)器 +1 時(shí),引發(fā) Page 的重新渲染,進(jìn)而引發(fā) Info 的重新渲染 

  // 但是由于 style 緩存了,因此不會(huì)觸發(fā) Info 內(nèi)的數(shù)據(jù)重新加載 

  return ( 

    <div> 

      <h4>計(jì)數(shù)值為:{count}</h4> 

      <button onClick={() => setCount(count + 1)}> +1 </button> 

      <Info style={style} /> 

    </div> 

  ); 

 

數(shù)據(jù)流

React Hooks 在數(shù)據(jù)流上帶來的變化有兩點(diǎn):一是支持更友好的使用 context 進(jìn)行狀態(tài)管理,避免層級(jí)過多時(shí)向中間層承載無關(guān)參數(shù);二是允許函數(shù)參與到數(shù)據(jù)流中,避免向下層組件傳入多余的參數(shù)。

useContext 作為 hooks 的核心模塊之一,可以獲取到傳入 context 的當(dāng)前值,以此達(dá)到跨層通信的目的。React 官網(wǎng)有著詳細(xì)的介紹,需要關(guān)注的是一旦 context 值發(fā)生改變,所有使用了該 context 的組件都會(huì)重新渲染。為了避免無關(guān)的組件重繪,我們需要合理的構(gòu)建 context ,比如從第一節(jié)提到的新思維模式出發(fā),按狀態(tài)的相關(guān)度組織 context,將相關(guān)狀態(tài)存儲(chǔ)在同一個(gè) context 中。

在過去,如果父子組件用到同一個(gè)數(shù)據(jù)請(qǐng)求方法 getData ,而該方法又依賴于上層傳入的 query 值時(shí),通常需要將 query 和 getData 方法一起傳遞給子組件,子組件通過判斷 query 值來決定是否重新執(zhí)行 getData。

class Parent extends React.Component { 

   state = { 

    query: 'keyword'

  } 

 

  getData() { 

    const url = `https://mocks.alibaba-inc.com/mock/fO87jdfKqX/demo/queryData.json?query=${this.state.query}`; 

    // 請(qǐng)求數(shù)據(jù)... 

    console.log(`請(qǐng)求路徑為:${url}`); 

  } 

 

  render() { 

    return ( 

      // 傳遞了一個(gè)子組件不渲染的 query 值 

      <Child getData={this.getData} query={this.state.query} /> 

    ); 

  } 

 

class Child extends React.Component { 

  componentDidMount() { 

    this.props.getData(); 

  } 

 

  componentDidUpdate(prevProps) { 

    // if (prevProps.getData !== this.props.getData) { // 該條件始終為 true 

    //   this.props.getData(); 

    // } 

    if (prevProps.query !== this.props.query) { // 只能借助 query 值來做判斷 

      this.props.getData(); 

    } 

  } 

 

  render() { 

    return ( 

      // ... 

    ); 

  } 

在 React Hooks 中 useCallback 支持我們緩存某一函數(shù),當(dāng)且僅當(dāng)依賴項(xiàng)發(fā)生變化時(shí),才更新該函數(shù)。這使得我們可以在子組件中配合 useEffect ,實(shí)現(xiàn)按需加載。通過 hooks 的配合,使得函數(shù)不再僅僅是一個(gè)方法,而是可以作為一個(gè)值參與到應(yīng)用的數(shù)據(jù)流中。

function Parent() { 

  const [count, setCount] = useState(0); 

  const [query, setQuery] = useState('keyword'); 

 

  const getData = useCallback(() => { 

    const url = `https://mocks.alibaba-inc.com/mock/fO87jdfKqX/demo/queryData.json?query=${query}`; 

    // 請(qǐng)求數(shù)據(jù)... 

    console.log(`請(qǐng)求路徑為:${url}`); 

  }, [query]);  // 當(dāng)且僅當(dāng) query 改變時(shí) getData 才更新 

 

  // 計(jì)數(shù)值的變化并不會(huì)引起 Child 重新請(qǐng)求數(shù)據(jù) 

  return ( 

    <> 

      <h4>計(jì)數(shù)值為:{count}</h4> 

      <button onClick={() => setCount(count + 1)}> +1 </button> 

      <input onChange={(e) => {setQuery(e.target.value)}} /> 

      <Child getData={getData} /> 

    </> 

  ); 

 

function Child({ 

  getData 

}) { 

  useEffect(() => { 

    getData(); 

  }, [getData]);    // 函數(shù)可以作為依賴項(xiàng)參與到數(shù)據(jù)流中 

 

  return ( 

    // ... 

  ); 

總結(jié)

回到最初的問題:“ hooks 有哪些易造成內(nèi)存泄露的點(diǎn)?”,我理解造成內(nèi)存泄露風(fēng)險(xiǎn)的在于 hooks 所帶來的作用域的變化。由于每次渲染都是獨(dú)立的,一旦有副作用引用了局部變量,并且未在組件銷毀時(shí)及時(shí)釋放,那么就極易造成內(nèi)存泄露。關(guān)于如何更好的使用 hooks, Sandro Dolidze 在博客中列了一個(gè) checkList[4],我覺得是個(gè)不錯(cuò)的建議,可以幫助我們寫出正確的 hooks 應(yīng)用。

  1. 遵循 Hooks 規(guī)則;
  2. 不要在函數(shù)體中使用任何副作用,而是將其放到 useEffect 中執(zhí)行;
  3. 取消訂閱/處理/銷毀所有已使用的資源;
  4. 首選 useReducer 或 useState 的函數(shù)更新,以防止在鉤子中讀寫相同的值;
  5. 不要在 render 函數(shù)中使用可變變量,而是使用 useRef;
  6. 如果在 useRef 中保存的內(nèi)容的生命周期比組件本身小,那么在處理資源時(shí)不要釋放該值;
  7. 小心死循環(huán)和內(nèi)存泄露;
  8. 當(dāng)需要提高性能是,可以 memoize 函數(shù)和對(duì)象;
  9. 正確設(shè)置依賴項(xiàng)(undefined => 每次渲染; [a, b] => 當(dāng) a 或 b 改變時(shí);[] => 僅執(zhí)行一次);
  10. 在可復(fù)用用例中使用自定義 hooks.

本文主要從自身體感出發(fā),對(duì)比總結(jié)了在開發(fā)過程中,hooks 所帶來的變化以及如何去應(yīng)對(duì)這種變化。理解有誤之處,歡迎指正~

原文地址:https://juejin.cn/post/6901956745417981959

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 伊人逼逼| 紧缚调教一区二区三区视频 | 国产成人在线看 | 精品麻豆剧传媒av国产九九九 | 国产精品免费久久久久久 | 欧美日韩一区二区三区在线观看 | 久久久亚洲精品视频 | 成人在线观看免费爱爱 | 在线观看免费av网 | 超碰8| 久久久久久久久国产成人免费 | 这里只有久久精品 | 麻豆二区| 91色视频在线观看 | 欧美成人h版在线观看 | 亚洲精品一区 | 中文字幕在线观看第一页 | 亚洲视频在线观看 | 国产欧美精品区一区二区三区 | 操网| 91久久精品国产91久久 | 成人av一级 | 免费av在线 | 欧美大逼 | 亚洲永久免费 | 国产精品国产精品国产专区不卡 | 国产亚洲精品一区二区 | 超色视频在线观看 | 欧美一级视频 | 在线免费日韩 | 在线看av网址 | 九九r热 | 国产成人一区二区三区 | 欧美精品一二区 | av在线电影网站 | t66y最新地址一地址二69 | 久久精品网| 激情伊人| 91在线精品一区二区 | 美女久久久 | 一本一道久久a久久精品综合 |