FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

React那些事儿 #247

Open FrankKai opened 3 years ago

FrankKai commented 3 years ago
FrankKai commented 3 years ago

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>
FrankKai commented 3 years ago

import { MouseEvent } from 'react'是什么意思?SyntheticEvent是什么类型?

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

好文章:https://fettblog.eu/typescript-react/events/#1

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;
}
FrankKai commented 3 years ago

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。

FrankKai commented 3 years ago

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

const EnhancedComponent = higherOrderComponent(WrappedComponent)

HOC能解决什么问题?

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} />;
    }
  };
}
FrankKai commented 3 years ago

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

React.isValidElement

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

React.cloneElement

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

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

FrankKai commented 3 years ago

React中的React.Children如何使用?

React.Children是一个针对react的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)
FrankKai commented 3 years ago

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

React.createElement()

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

根据指定类型,返回一个新的React element。

类型这个参数可以是:

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

基于这种方式,可以封装出可重用的业务组件:表单业务组件,表格业务组件等等,会极大程度的解放生产力!

FrankKai commented 3 years ago

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

FrankKai commented 3 years ago

React中的事件绑定onClick={handleOnClick}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

FrankKai commented 3 years ago

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
FrankKai commented 3 years ago

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

FrankKai commented 2 years ago

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生效。

FrankKai commented 2 years ago

React的portal如何使用?

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

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