原文連接:
React那些事兒
React hooks那些事兒html
新環境從Vue轉到了React技術棧,這個過程仍是比較有趣的。前端
在React中會看到與Vue不少類似的地方,也有一些不一樣的地方,學習過程當中遇到一些疑惑,作了記錄。vue
FC<[interface]>
是什麼意思?主要用處及最簡寫法是怎樣的?import { MouseEvent } from 'react'
是什麼意思?SyntheticEvent是什麼類型?React.forwardRef
是什麼意思?useImperativeHandle是什麼意思?一般來講,useRef用於引用組件的Dom節點。Vue中的ref則是引用一個vue組件。與Vue不一樣,react中的ref不只僅是引用Dom節點,還能夠生成一個內存不變的對象引用。react
const [foo, setFoo] = useState(null); const handler = () => { setFoo("hello") } useEffect(() => { return () => { // 不管怎樣foo都是null,給useEffect的deps加入foo也不行 if (foo === "hello") { // do something... } } }, [])
const foo = useRef(null) const handler = () => { foo.current = "hello" } useEffect(() => { return () => { // foo.current爲hello if (foo.current === "hello") { // do something... } } }, [])
總結起來就是: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
瀏覽器執行階段:可見修改(DOM操做,動畫,過渡)->樣式規則計算->計算空間和位置->繪製像素內容->多個層合成
前四個階段都是針對元素的,最後一個是針對層的。由點到面。數組
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
渲染過程當中執行:用於不依賴渲染完成的性能優化,狀態一變動當即執行
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搞不定的沒必要要的重複渲染和重複執行問題。
假設組件層級較深,props須要一級一級往下傳,能夠說是props hell問題。
context方式封裝的組件,爲須要接受數據的組件,提供了一種跨組件層級傳遞,按需引入上級props的方式。
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) }
<!-- 組件包裹 --> 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( type, [props], [...children] )
根據指定類型,返回一個新的React element。
類型這個參數能夠是:
JSX寫法的組件,最終也會被解析爲React.createElement()的方式。若是使用JSX的方式的話,不須要顯式調用React.createElement()。
基於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...
基於這種方式,能夠封裝出可重用的業務組件:表單業務組件,表格業務組件等等,會極大程度的解放生產力!
FC<[interface]>
是什麼意思?主要用處及最簡寫法是怎樣的?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>
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 = ...
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<HTMLButtonElement>
約束僅觸發HTML button DOM的事件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; }
簡而言之,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轉發組件層級以來較輕,代碼可讀性和可維護性更高。
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。
期待和你們交流,共同進步:
- 微信公衆號: 大大大前端 / excellent_developers
- Github博客: 趁你還年輕233的我的博客
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
努力成爲優秀前端工程師!