自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

蛻變之始,useEffect 最后一種用法

開發(fā) 前端
React 開發(fā)指導思想是數(shù)據(jù)驅(qū)動 UI,因此在 React 程序中,我們總是會思考如何設(shè)計與 UI 保持一致的數(shù)據(jù),把解決問題的重心放在數(shù)據(jù)邏輯上。

在 React 官方文檔中,對于 useEffect 有這樣一句描述:Effects are an escape hatch from the React paradigm。

這句話怎么理解呢?我們要結(jié)合前面的哲學部分進行思考。React 開發(fā)指導思想是數(shù)據(jù)驅(qū)動 UI,因此在 React 程序中,我們總是會思考如何設(shè)計與 UI 保持一致的數(shù)據(jù),把解決問題的重心放在數(shù)據(jù)邏輯上。

但是這樣的思路并不能應(yīng)對所有場景。在一些特殊的場景里,我們需要跳出數(shù)據(jù)驅(qū)動 UI 的解題思路,例如為了避免出現(xiàn)性能瓶頸,在高頻率的事件監(jiān)聽中,我們會選擇直接使用原生 DOM 節(jié)點來解決問題。

意思就是說,如果你想要跳出 React 的環(huán)境使用其他的方式開發(fā),可以把 effect 作為一個對外接口。

官方文檔也對此有進一步的解釋:

They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM

這樣的 escape hatch 給 React 帶來了極大的靈活性。在一個復雜的項目中,我們可以使用 React 解決一部分邏輯,然后使用別的更合適的方案解決另外的問題,這樣的靈活性提高了 React 項目的上限。這也是 React 能參與到類似于 Figma 這樣龐大、復雜、對性能要求極高的項目中去的原因。

Figma

一、如何運用

useEffect 第二個參數(shù)為一個數(shù)組,當我們傳入的參數(shù)為一個空數(shù)組時,表示 effect 僅會在組件首次渲染完成時執(zhí)行。

useEffect(effect, [])

只要我們確保當前組件在程序運行過程中相對穩(wěn)定,不會隨時被刪除,那么我們就可以在 effect 中獲取原生 DOM 節(jié)點,并添加綁定事件,回歸到原生 DOM 開發(fā)思路中去,你甚至可以在這里繼續(xù)使用 jQuery。

我們也可以在這里放心使用百度地圖 javaScript sdk,從而完整的在 react 項目中嵌入百度地圖。

function App() {
  // ...
  useEffect(() => {
    var mp = new BMapGL.Map('map'); 
    mp.centerAndZoom(new BMapGL.Point(121.491, 31.233), 11); 
  }, [])

  // ...
}

或者與 echarts 結(jié)合使用。

function App() {
  const main = useRef(null)

  useEffect(() => {
    var myChart = echarts.init(main.current);

    // 指定圖表的配置項和數(shù)據(jù)
    var option = {
      title: {
        text: 'ECharts 入門示例'
      },
      tooltip: {},
      legend: {
        data: ['銷量']
      },
      xAxis: {
        data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']
      },
      yAxis: {},
      series: [
        {
          name: '銷量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20]
        }
      ]
    };

    // 使用剛指定的配置項和數(shù)據(jù)顯示圖表。
    myChart.setOption(option);
  }, [])

  return (
    <div id="main" ref={main}></div>
  )
}

其他的例子還有很多,我們這里使用一個案例來進一步感受 React 與原生 DOM 開發(fā)結(jié)合的方式。

二、需求

在長頁面滾動的過程中,我們常常會在頁面的頂部或者旁邊,放一個標識組件來告訴用戶頁面已經(jīng)滾動到什么位置了。

在滾動的過程中,當前選中狀態(tài)會自動變化到對應(yīng)的位置。接下來我們思考一下這樣的功能應(yīng)該如何實現(xiàn)。

本案例具體要實現(xiàn)的效果如圖:

三、實現(xiàn)

實現(xiàn)原理比較簡單,我們只需要判斷每個元素什么時候應(yīng)該出現(xiàn)在屏幕中即可。對于原生 DOM 而言,我們可以使用 getBoundingClientRect 來獲取元素對象在可視區(qū)域中的位置信息。

本案例中的判斷規(guī)則非常簡單粗暴,因此當同屏出現(xiàn)兩個目標元素時會存在規(guī)則沖突,實踐中的規(guī)則設(shè)計會更細致一些,判斷邏輯也會更復雜。

React 提供了 useRef 來獲取真實 DOM 對象。

const n1 = useRef<HTMLDivElement>(null)

... 

<div ref={n1}></div>

本案例中的主要內(nèi)容為我們前面章節(jié)中搜索的 demo,因此每個 demo 都是使用 Block 組件來包裹布局。

<Block className={s.container} title={td.title} desc={td.desc}>
  <div className={r.flex}>
    <input
      className={s.input}
      placeholder="請輸入您要搜索的內(nèi)容"
      onChange={(e) => setParam(e.target.value)}
    />
  </div>
  <List
    list={list}
    loading={loading}
    error={error}
    renderItem={(item) => (
      <div key={item} className={s.item}>{item}</div>
    )}
  />
</Block>

但是因為 Block 并沒有針對 ref 進行支持,因此我們可以在外面額外套一個 div 作為目標元素。

import Normal from './search/Normal'

... 

<div ref={n1}>
  <Normal />
</div>

在頁面滾動的過程中,目標元素相對于可視區(qū)域的位置會隨時發(fā)生變化。因此我們可以在 effect 中添加 document 的滾動事件監(jiān)聽。

useEffect(() => {
  document.addEventListener('scroll', () => {
    ...
  })
}, [])

設(shè)計一個 state 狀態(tài)用來對應(yīng)選中狀態(tài)。

const [current, setCurrent] = useState(0)

...

<div className={current == 0 ? `${s.item} ${s.active}` : s.item}>案例一</div>

這樣,我們只需要在滾動過程中,不停的判斷每個目標元素和視口的相對位置,當符合條件的目標元素出現(xiàn)在視口時,就設(shè)置 current 為對應(yīng)的值,功能就實現(xiàn)了。

useEffect(() => {
  document.addEventListener('scroll', () => {
    const n1y = n1.current?.getBoundingClientRect().y
    const n2y = n2.current?.getBoundingClientRect().y
    const n3y = n3.current?.getBoundingClientRect().y
    const n4y = n4.current?.getBoundingClientRect().y
    const n5y = n5.current?.getBoundingClientRect().y

    if (n1y > 0 && n1y < window.innerHeight) {
      setCurrent(0)
    }
    if (n2y > 0 && n2y < window.innerHeight) {
      setCurrent(1)
    }
    if (n3y > 0 && n3y < window.innerHeight) {
      setCurrent(2)
    }
    if (n4y > 0 && n4y < window.innerHeight) {
      setCurrent(3)
    }
    if (n5y > 0 && n5y < window.innerHeight) {
      setCurrent(4)
    }
  })
}, [])

當我們使用 ref 獲取真實 DOM 時,ref 可能會為 null,但是由于 effect 在組件渲染完成之后執(zhí)行,此時必定能獲取到真實 DOM,因此我們使用 ?. 跳過了為 null 的判斷。

const n1y = 
n1.current?.getBoundingClientRect().y

完整代碼如下:

import { useEffect, useRef, useState } from 'react'
import Normal from './search/Normal'
import Normal2 from './search/Normal2'
import Normal3 from './search/Normal3'
import Normal4 from './search/Normal4'
import Normal5 from './search/Normal5'

import s from './style.module.scss'

export default function EffectDemo() {
  const [current, setCurrent] = useState(0)
  const n1 = useRef<HTMLDivElement>(null)
  const n2 = useRef<HTMLDivElement>(null)
  const n3 = useRef<HTMLDivElement>(null)
  const n4 = useRef<HTMLDivElement>(null)
  const n5 = useRef<HTMLDivElement>(null)

  useEffect(() => {
    document.addEventListener('scroll', () => {
      const n1y = n1.current?.getBoundingClientRect().y as number
      const n2y = n2.current?.getBoundingClientRect().y as number
      const n3y = n3.current?.getBoundingClientRect().y as number
      const n4y = n4.current?.getBoundingClientRect().y as number
      const n5y = n5.current?.getBoundingClientRect().y as number

      if (n1y > 0 && n1y < window.innerHeight) {
        setCurrent(0)
      }
      if (n2y > 0 && n2y < window.innerHeight) {
        setCurrent(1)
      }
      if (n3y > 0 && n3y < window.innerHeight) {
        setCurrent(2)
      }
      if (n4y > 0 && n4y < window.innerHeight) {
        setCurrent(3)
      }
      if (n5y > 0 && n5y < window.innerHeight) {
        setCurrent(4)
      }
    })
  }, [])

  return (
    <div>
      <div className={s.nav}>
        <div className={current == 0 ? `${s.item} ${s.active}` : s.item}>案例一</div>
        <div className={current == 1 ? `${s.item} ${s.active}` : s.item}>案例二</div>
        <div className={current == 2 ? `${s.item} ${s.active}` : s.item}>案例三</div>
        <div className={current == 3 ? `${s.item} ${s.active}` : s.item}>案例四</div>
        <div className={current == 4 ? `${s.item} ${s.active}` : s.item}>案例五</div>
      </div>
      <div ref={n1}>
        <Normal />
      </div>
      <div ref={n2}>
        <Normal2 />
      </div>
      
      <div ref={n3}>
        <Normal3 />
      </div>

      <div ref={n4}>
        <Normal4 />
      </div>
      
      <div ref={n5}>
        <Normal5 />
      </div>
      
    </div>
  )
}

四、總結(jié)

大量的初級框架開發(fā)者被困在「試圖讓框架解決所有問題」的思想牢籠里,認為學會了框架似乎就完事了。然而事實上,靈活運用 React 的 escape hatch 特性是成為 React 高手的標志之一,他在指引學習者不要把學習目標局限在 React 中,這是我們蛻變的開始。

不要試圖讓 React 解決所有問題,讓他做擅長的事情。

接下來我們思考兩個問題,一個問題是,在上面的案例中,我并沒有移除事件綁定,這樣的行為是否會造成內(nèi)存泄露?第二個問題,我們期望封裝一個圖片組件,該組件需要支持懶加載的優(yōu)化特性:只有當圖片內(nèi)容進入到可視區(qū)域時,圖片才開始加載,這樣的圖片組件應(yīng)該如何封裝?

責任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2025-02-10 08:43:31

Java異步編程

2020-12-09 10:15:34

Pythonweb代碼

2020-12-23 10:10:23

Pythonweb代碼

2022-06-22 09:44:41

Python文件代碼

2022-07-07 10:33:27

Python姿勢代碼

2022-01-26 11:03:22

代碼C語言標志位

2017-01-22 16:35:02

iOSBlockCallback

2012-01-17 11:02:39

2023-04-17 08:11:56

編程語言helloworld編程

2015-01-21 15:35:58

開源

2023-06-02 15:26:37

光纖綜合布線

2015-08-03 09:36:01

賽迪翻譯

2015-08-31 09:27:21

語言界面UI

2017-06-22 16:46:45

2022-06-06 15:44:24

大數(shù)據(jù)數(shù)據(jù)分析思維模式

2010-06-07 09:29:21

云計算

2012-10-12 09:32:14

開源云開源云技術(shù)云標準

2016-07-29 00:43:22

數(shù)據(jù)驅(qū)動

2011-07-04 17:53:48

快速測試

2023-11-30 07:45:11

useEffectReact
點贊
收藏

51CTO技術(shù)棧公眾號