乐趣区

关于javascript:写了3个月React我学到了什么

原文链接:
React 那些事儿
React hooks 那些事儿

新环境从 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

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/reac…

基于这种形式,能够封装出可重用的业务组件:表单业务组件,表格业务组件等等,会极大水平的解放生产力!

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/que…

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

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

好文章:https://fettblog.eu/typescrip…

  • 用于事件类型束缚
  • 除了 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 的集体博客
  • SegmentFault 专栏:趁你还年老,做个优良的前端工程师

致力成为优良前端工程师!

退出移动版