react康复指南

97 阅读3分钟

不知不觉已经有一个多月时间没好好写过代码了,最近忽然发现react有关的东西有点忘了,所以赶紧趁着周末来恢复一下。

观前须知:并没有太难的东西,比较适合新手和很久没接触react的同学。因为本篇更多的是我对于一些react相关题的分享,所以并没有太多的解析,更多的关注点是题目本身。

认知训练-渲染篇

恢复一下渲染相关的知识点,这篇题目比较基础,因此我就直接在代码最下方贴出答案。

Memo & re-render

下面代码的输出是啥?

import React, { useState, useEffect, memo } from 'react'
import ReactDOM from 'react-dom'

function A() {
  console.log('A')
  return <B/>
}

const B = memo(() => {
  console.log('B')
  return <C/>
})

function C() {
  console.log('C')
  return null
}

function D() {
  console.log('D')
  return null
}

function App() {
  const [state, setState] = useState(0)
  useEffect(() => {
    setState(state => state + 1)
  }, [])
  console.log('App')
  return (
    <div>
      <A state={state}/>
      <D/>
    </div>
  )
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>)






// "App" "A" "B" "C" "D" "App" "A" "D"

Context & re-render 1

import React, { useState, memo, createContext, useEffect, useContext} from 'react'
import ReactDOM from 'react-dom'

const MyContext = createContext(0);

function B() {
  const count = useContext(MyContext)
  console.log('B')
  return null
}

const A = memo(() => {
  console.log('A')
  return <B/>
})

function C() {
  console.log('C')
  return null
}
function App() {
  const [state, setState] = useState(0)
  useEffect(() => {
    setState(state => state + 1)
  }, [])
  console.log('App')
  return <MyContext.Provider value={state}>
    <A/>
    <C/>
  </MyContext.Provider>
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>)






// "App" "A" "B" "C" "App" "B" "C"

Context & re-render 2

import React, { useState, createContext, useEffect, useContext} from 'react'
import ReactDOM from 'react-dom'

const MyContext = createContext(0);

function B({children}) {
  const count = useContext(MyContext)
  console.log('B')
  return children
}

const A = ({children}) => {
  const [state, setState] = useState(0)
  console.log('A')
  useEffect(() => {
    setState(state => state + 1)
  }, [])
  return <MyContext.Provider value={state}>
    {children}
  </MyContext.Provider>
}

function C() {
  console.log('C')
  return null
}

function D() {
  console.log('D')
  return null
}
function App() {
  console.log('App')
  return <A><B><C/></B><D/></A>
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>)






//App A B C D A B
    

认知训练-hooks篇

useEffect

import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const [state, setState] = useState(0)
  console.log(state)

  useEffect(() => {
    setState(state => state + 1)
  }, [])

  useEffect(() => {
    console.log(state)
    setTimeout(() => {
      console.log(state)
    }, 100)
  }, [])

  return null
}

ReactDOM.render(<App/>, document.getElementById('root'))

    

先说答案:0 0 1 0

解析:这里主要涉及到一个概念叫 "capture value",简单来说就是react每次渲染都是一个独立的过程,其中的绝大多数数据(像ref就是例外)它都不是你认为都“最新的值”,或者说是“不变“的。关于captur value可以看这里:juejin.cn/post/707941…

useRef

import React, { useRef, useEffect, useState } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const ref = useRef(null)
  const [state, setState] = useState(1)

  useEffect(() => {
    setState(2)
  }, [])

  console.log(ref.current?.textContent)

  return <div>
    <div ref={state === 1 ? ref : null}>1</div>
    <div ref={state === 2 ? ref : null}>2</div>
  </div>
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>)

答案:undefined 1

解析:useRef的简单应用。初始化渲染时为1,由于ref保持同一引用,第二次渲染时为1.

实践训练-hooks篇

来实现一个自定义hooks,useFocus。

先看用法:

function App() {
    //当目标input被选中时,isFocused为true。
    const [iRef, isFocused] = useFocus()
    return 
        <div>
            <input ref={iRef}/>
            {isFocused && <p>focused</p>}
        </div>
}

小提示:注意iRef动态绑定的时的处理。

测试用例:先给大家一个测试用例

export function App() {
  const [ref, isFocused] = useFocus<HTMLInputElement>();
  const [targetRef, setTargetRef] = React.useState(0);
  return <div>
    <button onClick={() => { setTargetRef((pre) => (pre + 1) % 2) }}>change current:{targetRef}</button>
    <input ref={targetRef == 0 ? ref : null} />
    <input ref={targetRef == 1 ? ref : null} />
    {isFocused && <p>focused</p>}
  </div>
}

思路:思路其实很简单,可以返回一个ref,且监听ref.current的"focus"事件即可。

import React, { Ref } from 'react'
export function useFocus<T extends HTMLElement>(): [Ref<T>, boolean] {
  const ref = React.createRef<T>();
  var [isFocused, setIsFocused] = React.useState(false);
  React.useEffect(() => {
    function onFocus() {
      setIsFocused(true);
    }
    function onBlur() {
      setIsFocused(false);
    }
    const el = ref.current;
    //小问题1: 这里不用el可以吗? 下文el改成ref.current
    if (el) {
      el.addEventListener('focus', onFocus)
      el.addEventListener('blur', onBlur)
    }
    return () => {
      if (el) {
        el.removeEventListener('focus', onFocus);
        el.removeEventListener('blur', onBlur);
      }
    }
  })
  return [ref, isFocused]
}

小问题1解答: 不可以,如果把el改为ref.current,那么对于我们的测试用例来说,它在执行销毁函数(line:18)时,ref已经被修改,此时ref.current获取到的是最新的dom节点。

拓展:

//该种写法摘自评论区
import React, { Ref, useRef, useCallback, useState } from 'react'
export function useFocus<T extends HTMLElement>(): [Ref<T>, boolean] {
  const [focused, setFocused] = useState(false)
  const ref = useRef<T>()

  const onFocus = () => {
    setFocused(true)
  }
  const onBlur = () => {
    setFocused(false)
  }

  const callbackRef = useCallback((node: T) => {
    if (ref.current) {
      ref.current.removeEventListener('focus', onFocus)
      ref.current.removeEventListener('blur', onBlur)
    }
    ref.current = node
    if(node) {
      ref.current.addEventListener('focus', onFocus)
      ref.current.addEventListener('blur', onBlur)
    }
  }, [])

  return [callbackRef, focused]
}

更多

本篇所以题目均出自bigfrontend.dev/react,感兴趣的同…