詳解 react hooks 面試題

1. 簡單介紹下什麼是 hooks,hooks 產生的背景?hooks 的優點?

hooks 是針對在使用 react 時存在以下問題而產生的:

針對上面提到的問題,react 團隊研發了 hooks,它主要有兩方面作用:

優點也很明顯:

2. 知道 hoc 和 render props 嗎,它們有什麼作用?有什麼弊端?

Render Props 組件和高階組件主要用來實現抽象和可重用性。

弊端就是高階組件和 Render Props 本質上都是將複用邏輯提升到父組件中,很容易產生很多包裝組件,帶來的「嵌套地域」。

由於所有抽象邏輯都被其他 React 組件所隱藏,應用變成了一棵沒有可讀性的組件樹。而那些可見的組件也很難在瀏覽器的 DOM 中進行跟蹤。

2.1 Render Props

什麼是 Render Props

render props 模式是一種非常靈活複用性非常高的模式,它可以把特定行爲或功能封裝成一個組件,提供給其他組件使用讓其他組件擁有這樣的能力。他把組件可以動態渲染的地方暴露給外部,你不用再關注組件的內部實現,只要把數據通過函數傳出去就好。

使用場景:

2.2 Hoc

hoc 是 React 中用於重用組件邏輯的高級技術,它是一個函數,能夠接受一個組件並返回一個新的組件。
實現高階組件的兩種方式:

2.2.1 屬性代理

a. 什麼是屬性代理

屬性代理組件繼承自 React.Component,通過傳遞給被包裝的組件 props 得名

// 屬性代理,把高階組件接收到的屬性傳遞給傳進來的組件
function HOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

b. 屬性代理的用途

/*
可以通過 ref 獲取關鍵詞 this(WrappedComponent 的實例)
當 WrappedComponent 被渲染後,ref 上的回調函數 proc 將會執行,此時就有了這個 WrappedComponent 的實例的引用
*/
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }
    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}

2.1.2 反向繼承

反向繼承是繼承自傳遞過來的組件

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

反向繼承允許高階組件通過 this 關鍵詞獲取 WrappedComponent,意味着它可以獲取到 state,props,組件生命週期(component lifecycle)鉤子,以及渲染方法(render),所以我們主要用它來做渲染劫持,比如在渲染方法中讀取或更改 React Elements tree,或者有條件的渲染等。

2.1.3 高階組件相關的面試題

  1. 這怎麼在高階組件裏面訪問組件實例 **
    答案見上面

  2. 你的項目中怎麼使用的高階組件 **
    a. 項目中經常存在在配置系統中配置開關 / 全局常量,然後在頁面需要請求配置來做控制,如果在每個需要調用全局設置的地方都去請求一下接口,就會有一種不優雅的感覺,這個時候我就想到利用高階組件抽象一下。
    b. 項目開發過程中,經常也會遇到需要對當前頁面的一些事件的默認執行做阻止,我們也可以寫一個高階組件等。

  3. hooks 和 hoc 和 render props 有什麼不同?

它們之間最大的不同在於,後兩者僅僅是一種開發模式,而自定義的 hooks 是 react 提供的 API 模式,它既能更加自然的融入到 react 的渲染過程也更加符合 react 的函數編程理念。

  1. 介紹下常用的 hooks?
// 我們實現一個簡易版的useState
let memoizedStates = [ ]  // 多個useState 時需要使用數組來存
let index = 0
function useState (initialState) {
   memoizedStates[index] = memoizedStates[index] || initialState
   let currentIndex = index;
   function setState (newState) {
      memoizedStates[currentIndex] = newState
      render()
   }
   return [memoizedStates[index++], setState]
}
// componentDidMount,必須加[],不然會默認每次渲染都執行
useEffect(()=>{
}, [])

// componentDidUpdate
useEffect(()=>{
document.title = `You clicked ${count} times`;
return()=>{
// 以及 componentWillUnmount 執行的內容 
}
}, [count])

//  shouldComponentUpdate, 只有 Parent 組件中的 count state 更新了,Child 纔會重新渲染,否則不會。
function Parent() {
   const [count,setCount] = useState(0);
   const child = useMemo(()=> <Child count={count} />, [count]);
   return <>{count}</>
}

function Child(props) {
    return <div>Count:{props.count}</div>
}

這裏有一個點需要注意,就是默認的 useEffect(不帶 [])中 return 的清理函數,它和 componentWillUnmount 有本質區別的,默認情況下 return,在每次 useEffect 執行前都會執行,並不是只有組件卸載的時候執行。
useEffect 在副作用結束之後,會延遲一段時間執行,並非同步執行,和 compontDidMount 有本質區別。遇到 dom 操作,最好使用 useLayoutEffect。

  1. hooks 中的坑,以及爲什麼?
const [firstData, setFirstData]: any = useState([]);
const handleFirstAdd = () => {
  // let temp = firstData
  // 不要這麼寫,直接修改原數組相當於沒有更新
  let temp = [...firstData];
  // 必須這麼寫,多層數組也要這麼寫
  temp.push({
    value: "",
  });
  setFirstData(temp);
};

function Indicatorfilter() {
  let [num, setNums] = useState([0, 1, 2, 3]);
  const test = () => {
    // 這裏坑是直接採用push去更新num,setNums(num)是無法更新num的,必須使用num = [...num ,1]
    num.push(1);
    // num = [...num ,1]
    setNums(num);
  };
  return (
    <div class>
      <div onClick={test}>測試</div>
      <div>
        {num.map((item, index) => (
          <div key={index}>{item}</div>
        ))}
      </div>
    </div>
  );
}

class Indicatorfilter extends React.Component<any, any> {
  constructor(props: any) {
    super(props);
    this.state = {
      nums: [1, 2, 3],
    };
    this.test = this.test.bind(this);
  }
  test() {
    // class採用同樣的方式是沒有問題的
    this.state.nums.push(1);
    this.setState({
      nums: this.state.nums,
    });
  }
  render() {
    let { nums } = this.state;
    return (
      <div>
        <div onClick={this.test}>測試</div>
        <div>
          {nums.map((item: any, index: number) => (
            <div key={index}>{item}</div>
          ))}
        </div>
      </div>
    );
  }
}

useState 設置狀態的時候,只有第一次生效,後期需要更新狀態,必須通過 useEffect

TableDeail 是一個公共組件,在調用它的父組件裏面,我們通過 set 改變 columns 的值,以爲傳遞給 TableDeail 的 columns 是最新的值,所以 tabColumn 每次也是最新的值,但是實際 tabColumn 是最開始的值,不會隨着 columns 的更新而更新

const TableDeail = ({
    columns,
}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
}

正確的做法是通過 useEffect 改變這個值

const TableDeail = ({
    columns,
}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])
}

原文地址: https://blog.csdn.net/kellywong/article/details/106430977

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ebdz3NeSXFYNrDqmKDkahQ