寫了3個月React,我學到了什麼?

image.png

原文連接:
React那些事兒
React hooks那些事兒html

新環境從Vue轉到了React技術棧,這個過程仍是比較有趣的。前端

在React中會看到與Vue不少類似的地方,也有一些不一樣的地方,學習過程當中遇到一些疑惑,作了記錄。vue

  • useRef如何解決空指針問題?
  • useEffect與useCallback(useMemo)的區別是什麼?
  • React除了能夠經過props傳遞數據之外,如何經過context方式傳遞數據?
  • React.createElement(Input, props)中的React.createElement如何理解?
  • react中的FC是什麼?FC<[interface]>是什麼意思?主要用處及最簡寫法是怎樣的?
  • React中FC的形參的props, context, propTypes, contextTypes, defaultProps, displayName是什麼?
  • import { MouseEvent } from 'react'是什麼意思?SyntheticEvent是什麼類型?
  • React.forwardRef是什麼意思?useImperativeHandle是什麼意思?

useRef如何解決空指針問題?

一般來講,useRef用於引用組件的Dom節點。Vue中的ref則是引用一個vue組件。與Vue不一樣,react中的ref不只僅是引用Dom節點,還能夠生成一個內存不變的對象引用。react

使用useState致使的空指針示例

const [foo, setFoo] = useState(null);

const handler = () => {
    setFoo("hello")
}

useEffect(() => {
    return () => {
      // 不管怎樣foo都是null,給useEffect的deps加入foo也不行
      if (foo === "hello") {
          // do something...
      }
    }
}, [])

使用useRef的正確示例(解決事件處理器中對象爲null的問題)

const foo = useRef(null)

const handler = () => {
    foo.current = "hello"
}

useEffect(() => {

    return () => {
      // foo.current爲hello
      if (foo.current === "hello") {
          // do something...
      }
    }
}, [])

useRef解決空指針問題的緣由是什麼?

  • 組件生命週期期間,useRef指向的對象都是一直存在的
  • 每次渲染時,useRef都指向同一個引用的對象

總結起來就是:useRef生成的對象,在組件生命週期期間內存地址都是不變的。git

const refContainer = useRef(initialValue);

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.github

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.typescript

總結一下會使用到useRef解決空指針問題的場景:segmentfault

  • 事件處理器
  • setTimeout,setInterval

useEffect與useCallback(useMemo)的區別是什麼?

瀏覽器執行階段:可見修改(DOM操做,動畫,過渡)->樣式規則計算->計算空間和位置->繪製像素內容->多個層合成
前四個階段都是針對元素的,最後一個是針對層的。由點到面。
image數組

執行時間不一樣

useEffect在渲染完成後執行函數,更加準確的來講是在layout和paint完成以後。瀏覽器

The function passed to useEffect will run after the render is committed to the screen.Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint

useCallback(useMemo)在渲染過程當中執行函數。

Remember that the function passed to useMemo runs during rendering.

哪些適合在渲染完成後執行,哪些適合在渲染過程當中執行

渲染完成後執行:Mutations(DOM操做), subscriptions(訂閱), timers, logging
渲染過程當中執行:用於不依賴渲染完成的性能優化,狀態一變動當即執行

一個例子闡明useEffect和useMemo的區別

useMemo最主要解決的問題:怎麼在DOM改變的時候,控制某些函數不被觸發。
例以下面這個例子,在name變動的時候,useEffect會在DOM渲染完成後出發price的函數,而useMemo能夠精準的只觸發更新name的函數。

這是一個很是很是好的例子,更加詳細的博文在這裏:useMemo和useEffect有什麼區別?怎麼使用useMemo

import React, {Fragment} from 'react'
import { useState, useEffect, useCallback, useMemo } from 'react'

const nameList = ['apple', 'peer', 'banana', 'lemon']
const Example = (props) => {
    const [price, setPrice] = useState(0)
    const [name, setName] = useState('apple')
    
    
    function getProductName() {
        console.log('getProductName觸發')
        return name
    }
    // 只對name響應
    useEffect(() => {
        console.log('name effect 觸發')
        getProductName()
    }, [name])
    
    // 只對price響應
    useEffect(() => {
        console.log('price effect 觸發')
    }, [price])
  
    // memo化的getProductName函數   🧬🧬🧬
    const memo_getProductName = useMemo(() => {
        console.log('name memo 觸發')
        return () => name  // 返回一個函數
    }, [name])

    return (
        <Fragment>
            <p>{name}</p>
            <p>{price}</p>
            <p>普通的name:{getProductName()}</p>
            <p>memo化的:{memo_getProductName ()}</p>
            <button onClick={() => setPrice(price+1)}>價錢+1</button>
            <button onClick={() => setName(nameList[Math.random() * nameList.length << 0])}>修更名字</button>
        </Fragment>
    )
}
export default Example

點擊價錢+1按鈕(經過useMemo,多餘的memo_getProductName ()沒有被觸發,只觸發price相關的函數)

getProductName觸發
price effect 觸發

點擊修更名字按鈕(經過useEffect,只觸發name相關)

name memo 觸發
getProductName觸發
name effect 觸發
getProductName觸發
總結

useEffect面對一些依賴於某個state的DOM渲染時,會出現一些性能問題,而useMemo能夠優化這個問題。
最後,用一句話來歸納useMemo的話,那就是:useMemo能夠避免一些useEffect搞不定的沒必要要的重複渲染和重複執行問題。

React除了能夠經過props傳遞數據之外,如何經過context方式傳遞數據?

假設組件層級較深,props須要一級一級往下傳,能夠說是props hell問題。
context方式封裝的組件,爲須要接受數據的組件,提供了一種跨組件層級傳遞,按需引入上級props的方式。

組件定義context部分

import * as React from 'react'
// myContext.ts
interface IContext {
     foo: string,
     bar?: number,
     baz: string
}
const myContext = React.createContext<IContext>({
     foo: "a",
     baz: "b"
})


interface IProps {
    data: IContext ,
}

const myProvider: React.FC<IProps> = (props) => {
     const {data, children} = props
     return <myContext.Provider value={data}>{children}</myContext.Provider>
}

export default myProvider;

export function useMyContext() {
  return useContext(myContext)
}

使用組件和context部分

<!-- 組件包裹 -->
import myProvider from './myContext.ts'

<myProvider data={{foo: "foo", baz: "baz"}}>
    <div className="root">
        <div className="parent">
            <Component1 />
            <Component2 />
        </div>
     </div>
</myProvider>
// Component1
import  {useMyContext} from './myContext.ts'
const {foo, baz} = useMyContext()

const Compoonent1 = () => {
    return (<div>{foo}{baz}</div>)
}
export Component1

React.createElement(Input, props)中的React.createElement如何理解?

React.createElement()

React.createElement(
    type,
    [props],
    [...children]
)

根據指定類型,返回一個新的React element。

類型這個參數能夠是:

  • 一個「標籤名字符串」(例如「div」,「span」)
  • 一個React component 類型(一個class或者一個function)
  • 一個React fragment 類型

JSX寫法的組件,最終也會被解析爲React.createElement()的方式。若是使用JSX的方式的話,不須要顯式調用React.createElement()。

React.createElement(Input, props)

基於antd,封裝通用表單組件方法。

// generator.js
import React from "react";
import { Input, Select } from "antd";

const components = {
  input: Input,
  select: Select
};

export default function generateComponent(type, props) {
  return React.createElement(components[type], props);
}

簡單使用這個通用表單組件方法:

import generateComponent from './generator'

const inputComponent = generateComponent('input', props)
const selectComponent = generateComponent('select', props)

你可能會以爲上面這種方式比較雞肋,可是若是批量地生成組件,這種方式就頗有用了。

// components.js
import React from "react";
import generateComponent from "./generator";

const componentsInfos = [
  {
    type: "input",
    disabled: true,
    defaultValue: "foo"
  },
  {
    type: "select",
    autoClear: true,
    dropdownStyle: { color: "red" }
  }
];

export default class Components extends React.Component {
  render() {
    return componentsInfos.map((item) => {
      const { type, ...props } = item;
      return <>{generateComponent(type, props)}</>;
    });
  }
}

具體的示例能夠查看:https://codesandbox.io/s/reac...

基於這種方式,能夠封裝出可重用的業務組件:表單業務組件,表格業務組件等等,會極大程度的解放生產力!

react中的FC是什麼?FC<[interface]>是什麼意思?主要用處及最簡寫法是怎樣的?

react中的FC是什麼?

type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

FC是FunctionComponent的縮寫,FunctionComponent是一個泛型接口。

FC<[interface]>是什麼意思?

是爲了提供一個函數式組件環境,用於包裹組件。
爲何呢?由於在函數式組件內部可使用hooks。

函數式組件

const Component = (props) => {
    // 這裏可使用hooks
    return <div />
}
或者
function Component(props) {
  // 這裏可使用hooks
  return <div />;
}

主要用處及最簡寫法是怎樣的?

項目內的公共函數式組件,做爲組件容器使用,用於提供hooks上下文環境。

// Container.js
import React, { FC } from 'react'

interface IProps {
     children: any
}

const Container: FC<IProps> = (props) =>  {
  return (
    <div>
      {props.children}
    </div>
  )
}

export default Container
// 使用
<Container>
    <Component1 />
    <Component2 />
</Container>

React中FC的形參的props, context, propTypes, contextTypes, defaultProps, displayName是什麼?

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
}

type PropsWithChildren<P> = P & { children?: ReactNode };

其中props和context都是函數組件的形參。
而propTypes,contextTypes,defaultProps,displayName都是組件的函數組件的屬性。

const Foo: FC<{}> = (props, context) => {
    return (
        <div>{props.children}</div>
    )
}
Foo.propTypes = ...
Foo.contextTypes = ...
Foo.defaultProps = ...
Foo.displayName = ...

react函數式組件與純函數組件有什麼區別呢?

1.react函數式組件必須返回ReactElement或者null,純函數組件返回值沒有限定
2.react函數式組件的props限定children的類型爲ReactNode,純函數組件沒有限定
3.react函數式組件擁有propTypes,contextTypes,defaultProps,displayName等等類型約束,純函數組件沒有限定

https://stackoverflow.com/que...

import { MouseEvent } from 'react'是什麼意思?SyntheticEvent是什麼類型?

import { MouseEvent } from 'react'是什麼意思?

好文章:https://fettblog.eu/typescrip...

  • 用於事件類型約束
  • 除了MouseEvent,還有AnimationEvent, ChangeEvent, ClipboardEvent, CompositionEvent, DragEvent, FocusEvent, FormEvent, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent, TransitionEvent, WheelEvent. As well as SyntheticEvent
  • 可使用MouseEvent<HTMLButtonElement>約束僅觸發HTML button DOM的事件
  • InputEvent較爲特殊,由於是一個實驗事件,所以能夠用SyntheticEvent替代

SyntheticEvent是什麼類型?

Synthetic -> 合成的

在React中,幾乎全部的事件都繼承了SyntheticEvent這個interface。
SyntheticEvent是一個跨瀏覽器的瀏覽器事件wrapper,一般用於替代InpuEvent這樣的事件類型。

interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
interface BaseSyntheticEvent<E = object, C = any, T = any> {
    nativeEvent: E;
    currentTarget: C;
    target: T;
    bubbles: boolean;
    cancelable: boolean;
    defaultPrevented: boolean;
    eventPhase: number;
    isTrusted: boolean;
    preventDefault(): void;
    isDefaultPrevented(): boolean;
    stopPropagation(): void;
    isPropagationStopped(): boolean;
    persist(): void;
    timeStamp: number;
    type: string;
}

React.forwardRef是什麼意思?useImperativeHandle是什麼意思?

簡而言之,refs轉發就是爲了獲取到組件內部的DOM節點。
React.forwardRef意思是Refs轉發,主要用於將ref自動經過組件傳遞到某一子組件,常見於可重用的組件庫中。

在使用forwardRef時,可讓某些組件接收ref,而且將其向下傳遞給子組件,也能夠說是」轉發「給子組件。

沒有使用refs轉發的組件。

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

使用refs轉發的組件。

const FancyButton = React.forwardRef((props, ref)=>{
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
})

如何使用?

// 建立一個ref變量
const ref = React.createRef();
// 將ref變量傳入FancyButton,FancyButton將ref變量轉發給button
<FancyButton ref={ref}></FancyButton>
// ref.current指向button DOM節點

vue中也有refs機制不一樣,可是vue若是想獲取到子組件內部的DOM節點,須要一級一級的去獲取,好比this.$refs.parent.$refs.child,這會致使組件層級依賴嚴重。
相比vue而言,React的refs轉發組件層級以來較輕,代碼可讀性和可維護性更高。

useImperativeHandle是什麼意思?

import React, { useRef, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';

const FancyInput = React.forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    publicFocus: () => {
      inputRef.current.focus();
    }
  }));

  return <input ref={inputRef} type="text" />
});

const App = props => {
  const fancyInputRef = useRef();

  return (
    <div>
      <FancyInput ref={fancyInputRef} />
      <button
        onClick={() => fancyInputRef.current.publicFocus()}
      >父組件調用子組件的 focus</button>
    </div>
  )
}

ReactDOM.render(<App />, root);

上面這個例子中與直接轉發 ref 不一樣,直接轉發 ref 是將 React.forwardRef 中函數上的 ref 參數直接應用在了返回元素的 ref 屬性上,其實父、子組件引用的是同一個 ref 的 current 對象,官方不建議使用這樣的 ref 透傳,而使用 useImperativeHandle 後,可讓父、子組件分別有本身的 ref,經過 React.forwardRef 將父組件的 ref 透傳過來,經過 useImperativeHandle 方法來自定義開放給父組件的 current。

期待和你們交流,共同進步:

努力成爲優秀前端工程師!
相關文章
相關標籤/搜索