百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

写了3个月React,我学到了什么?

xsobi 2024-12-10 21:36 1 浏览

原文链接: 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为hello
      if (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.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/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) => {
    // 这里可以使用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/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} />
      <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的个人博客[4]

?SegmentFault专栏:趁你还年轻,做个优秀的前端工程师[5]

努力成为优秀前端工程师!

References

[1] React那些事儿: https://github.com/FrankKai/FrankKai.github.io/issues/247
[2] React hooks那些事儿:
https://github.com/FrankKai/FrankKai.github.io/issues/248
[3] useMemo和useEffect有什么区别?怎么使用useMemo:
https://www.jianshu.com/p/94ace269414d
[4] 趁你还年轻233的个人博客:
https://github.com/FrankKai/FrankKai.github.io
[5] 趁你还年轻,做个优秀的前端工程师:
https://segmentfault.com/blog/chennihainianqing

相关推荐

斗鱼针针成旻云个人资料 针针年龄身高演艺经历介绍

[闽南网]针针成旻云曾是七煌旗下签约艺人,经常在斗鱼进行直播身高超过170cm的她更因为有一双大长腿而被称为“斗鱼第一美腿”、“电竞第一腿”。本文这就来对针针的个人资料进行详细介绍,想知道她的年龄身高...

轻量级RTSP服务模块和RTSP推流模块适用场景区别

好多开发者一直搞不清轻量级RTSP服务SDK和RTSP推流SDK的区别,以下是相关区别:1.轻量级RTSP服务模块:轻量级RTSP服务解决的核心痛点是避免用户或者开发者单独部署RTSP或者RTMP服...

《新·奥特曼》11月18日国内视频平台上线

《新·奥特曼》海报。新京报讯11月14日,由上海新创华文化发展有限公司授权引进电影《新·奥特曼》宣布正式定档11月18日(周五)00:00上线视频平台,上线版本为日语配音中文字幕版。影片由庵野秀明(...

剑指Apple Watch!Android Wear也将支持视频功能

想必智能手表发展到现在,大家最期待的还是视频功能,近日AndroidWear就实现了这一功能,以后就能在手表上看视频了,想想就挺激动的,快来看看吧!其实早在WWDC大会上,老对手AppleWatc...

QT应用编程:基于VLC开发音视频播放器(句柄方式)

一、环境介绍操作系统:win1064位QT版本:QT5.12.6编译器:MinGW32VLC版本:...

OBS 源码分析 obs开发

本文将按照数据源的获取、渲染、推送的直播流程来让大家深入了解一下。1、直播源数据获取obs在启动时会优先加载libobs核心库,这个库初始化obs很多内容,包括crash模块、com、性能监...

Android和iOS端Moments更新:支持视频分享功能

Moments是社交网络巨头Facebook推出的一款私人照片分享应用,今天公司宣布对Android端和iOS端应用同时升级,新增对视频分享功能的支持。事实上早在数周之前,Facebook就曾表示Mo...

您很快就可以在Android Galaxy设备之间传输视频通话

在阅读此文之前,辛苦点击右上角的“关注”,既方便您进行讨论与分享,又能给您带来不一样的参与感,感谢您的支持!导语:在科技领域,每时每刻都有新的发展,令人兴奋的创新不断涌现。早在八月份,Android系...

一篇文章带你FFmpeg到流媒体服务器开发

安装ffmpeg:下载FFmpeg和libx264的包ffmpeg-2.4.1.tar.bz2last_x264.tar.bz2libx264需要yasm,所以先安装yasmapt-getinst...

YouTube 为 Android 平台提供 1440P 视频

安锋网8月10日消息,Android从起初的480P的屏幕分辨率发展到2014年的1440P花了将近六年的时间,一般认为1080P的屏幕分辨率已经是人眼可以识别的极限,但是...

FFmpeg 调用 Android MediaCodec 进行硬解码(附源码)

FFmpeg在3.1版本之后支持调用平台硬件进行解码,也就是说可以通过FFmpeg的C代码去调用Android上的MediaCodec了。在官网上有对应说明,地址如下:trac....

Android FFmpeg + OpenGL ES YUV Player

1、FFmpeg解出YUV帧数据1.1方法介绍打开封装格式上下文...

基于WebRTC的Android移动端无线视频传输

摘要:视频传输技术在现代社会广泛应用,人们对其的要求也越来越高,其发展的趋势是方便、快捷、随时随地。传统的视频传输过于依赖线路,线路的走向限制了传输的很多可能,所以无线传输才是发展的方向。本文提出...

使用python爬取抖音app视频 python爬取抖音视频数据

记录一下如何用python爬取app数据,本文以爬取抖音视频app为例。编程工具:pycharm...

Android IOS WebRTC 音视频开发总结(七七)-- WebRTC的架构和协议栈

本文主要介绍WebRTC的架构和协议栈(我们翻译和整理的,译者:litie),最早发表在【编风网】为了便于理解,我们来看一个最基本的三角形WebRTC架构(见下图)。在这个架构中,移动电话用“浏览器M...