
文章插图
原文链接: React那些事儿[1] React hooks那些事儿[2]
新环境从Vue转到了React技术栈,这个过程还是比较有趣的 。
在React中会看到与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节点,还可以生成一个内存不变的对象引用 。
使用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为helloif (foo.current === "hello") {// do something...}}}, [])
useRef解决空指针问题的原因是什么??组件生命周期期间,useRef指向的对象都是一直存在的?每次渲染时 , useRef都指向同一个引用的对象
总结起来就是:useRef生成的对象,在组件生命周期期间内存地址都是不变的 。
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.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.
总结一下会使用到useRef解决空指针问题的场景:
?事件处理器
?setTimeout , setInterval
useEffect与useCallback(useMemo)的区别是什么?浏览器执行阶段:可见修改(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 渲染过程中执行:用于不依赖渲染完成的性能优化,状态一变更立即执行
一个例子阐明useEffect和useMemo的区别useMemo最主要解决的问题:怎么在DOM改变的时候 , 控制某些函数不被触发 。例如下面这个例子 , 在name变更的时候 , useEffect会在DOM渲染完成后出发price的函数,而useMemo可以精准的只触发更新name的函数 。
这是一个非常非常好的例子,更加详细的博文在这里:useMemo和useEffect有什么区别?怎么使用useMemo[3]
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.tsinterface 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} = propsreturn <myContext.Provider value=https://www.baibaike.com/baike/{data}>{children}}export default myProvider;export function useMyContext() {return useContext(myContext)}
使用组件和context部分<!-- 组件包裹 -->import myProvider from './myContext.ts'<myProvider data=https://www.baibaike.com/baike/{{foo: "foo", baz: "baz"}}>
// Component1import{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.jsimport 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.jsimport 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/react-component-generator-onovg?file=/src/index.js基于这种方式,可以封装出可重用的业务组件:表单业务组件 , 表格业务组件等等,会极大程度的解放生产力!
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) => {// 这里可以使用hooksreturn <div />}或者function Component(props) {// 这里可以使用hooksreturn <div />;}
主要用处及最简写法是怎样的?项目内的公共函数式组件,作为组件容器使用 , 用于提供hooks上下文环境 。// Container.jsimport 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/questions/53935996/whats-the-difference-between-a-react-functioncomponent-and-a-plain-js-function
import { MouseEvent } from 'react'是什么意思?SyntheticEvent是什么类型?import { MouseEvent } from 'react'是什么意思?好文章:https://fettblog.eu/typescript-react/events/#1
?用于事件类型约束
? 除了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} /><buttononClick={() => 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的个人博客[4]
?SegmentFault专栏:趁你还年轻,做个优秀的前端工程师[5]
努力成为优秀前端工程师!
References【写了3个月React react什么意思】[1] React那些事儿:
- 为什么我不再用Vue react什么意思
- 面试官 react什么意思
- 一文让你了解微前端的现状 react什么意思
- React入门知识 react什么意思
- 浙江金华双龙洞_《记金华的双龙洞》作者按的先后顺序,先写了...
- 架子鼓教学_初学用电子鼓还是架子鼓 学了3个月了。老师教学和...
- 草原主要内容概括 草原写了什么
- 1-3个月的小宝宝穿衣选择方法
- 脐橙常温一般能放多久
- 工作计划怎么写_未来工作计划设想怎么写了?