FrankKai / FrankKai.github.io

FE blog

Home Page:https://frankkai.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React那些事儿

FrankKai opened this issue · comments

  • react中的FC是什么?FC<[interface]>是什么意思?主要用处及最简写法是怎样的?
  • import { MouseEvent } from 'react'是什么意思?SyntheticEvent是什么类型?
  • React.forwardRef是什么意思?useImperativeHandle是什么意思?
  • React中的HOC(高阶组件)是什么意思?
  • React中的isValidElement和cloneElement是什么意思?
  • React中的React.Children如何使用?
  • React.createElement(Input, props)中的React.createElement如何理解?
  • React中FC的形参的props, context, propTypes, contextTypes, defaultProps, displayName是什么?
  • React中的事件绑定onClick={handleOnClick}与onClick={()=>handleOnClick('pop')}区别是什么?
  • React除了可以通过props传递数据以外,如何通过context方式传递数据?
  • React的children无法slice怎么办?
  • React18引入的auto batch没有生效是什么原因?

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>

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。

React中的HOC(高阶组件)是什么意思?

  • HOC主要用于复用组件逻辑
  • HOC本身不是React中的一部分,是React组合性质的一种模式
  • 具体来说,一个HOC组件接收一个组件,再返回一个新的组件出去
  • 普通组件是将props转化为UI,而HOC组件是将组件转化为另一个组件
  • redux中的connect就是一个HOC组件
const EnhancedComponent = higherOrderComponent(WrappedComponent)

HOC能解决什么问题?

  • HOC可以解决横切关注点问题,是一种比mixins更好的方案。
  • HOC可以抽象出一组逻辑,一次定义,多处复用和共享它。
  • HOC依赖传入的props,组件替换容易。

HOC的特点

  • 不会修改传入的组件,不会使用继承
  • 是一个纯函数,没有副作用

一个数据源增加订阅,取消订阅的HOC:

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
  // ...and returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... and renders the wrapped component with the fresh data!
      // Notice that we pass through any additional props
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

React中的isValidElement和cloneElement是什么意思?

React.isValidElement

React.isValidElement(object) // 验证对象是否是React element,返回true或者false

React.cloneElement

React.cloneElement(element, [props], [...children])

克隆并返回一个新的React元素element。新生成的元素具有原来元素的属性,并且会合并自己的props,新的children替换旧的children。

React中的React.Children如何使用?

React.Children是一个针对react的children节点的工具函数

  • map 映射children,若为null/undefined,返回null/undefined
  • forEach 遍历children
  • count 统计child个数
  • only 是否只有一个孩子并且返回它,若不是则抛出error
  • toArray,抹平children
React.Children.map(children, function[(thisArg)])
React.Children.forEach(children, function[(thisArg)])
React.Children.count(children)
React.Children.only(children)
React.Children.toArray(children)

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的形参的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

React中的事件绑定onClick={handleOnClick}onClick={()=>handleOnClick('pop')}区别是什么?

  • 不需要传递数据:onClick={handleOnClick}
  • 需要传递数据:onClick={()=>handleOnClick('pop')}
  • 错误写法:onClick={handleOnClick('pop')}

印象很深刻的踩坑:假设将注释代码放出来,则会提醒Cannot read property 'mutateArray' of null

这是因为onClick接收的类型为onClick?: MouseEventHandler<T>;,其实就是函数类型,而onClick={handleOnClick('pop')}中handleOnClick('pop')返回的是undefined,并不是一个事件处理器类型。

image

代码可以查看这个在线demo:https://codesandbox.io/s/visual-array-n3ky8

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的children无法slice怎么办?

假如你想获取children位置1和2的切片,很有可能写出这样的代码。
本质上就是想操作children元素。

children.slice(1,3)

但是React会报错:

Property 'slice' does not exist on type 'ReactNode'.

如何解决?
使用React.Children.toArray()即可。

React.Children.toArray(children).slice(1,3)

React官网原文:

Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.

https://reactjs.org/docs/react-api.html#reactchildren

React18引入的auto batch没有生效是什么原因?

有可能是使用了StrictMode。

在线demo:https://codesandbox.io/s/react-18-auto-batch-blldfn?file=/src/index.js:0-268

开启StrictMode,每次触发点击事件,打印2 次,auto batch失效;关闭StrictMode,每次触发点击事件,打印1 次,auto batch生效。

React的portal如何使用?

React的portal,提供了一种将节点挂载到其他dom节点上的能力,而不是直接挂载到当前父组件的下面。

// 将props.children挂载到父节点
return (
  <div>
    {props.children}
  </div>
);
// 将props.children挂载到其他dom节点
return ReactDOM.createPortal(
  props.children,
  domNode
);