原文链接: 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完成之后。
useCallback(useMemo)在渲染过程中执行函数。
哪些适合在渲染完成后执行,哪些适合在渲染过程中执行
渲染完成后执行: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相关的函数)
点击修改名字按钮(通过useEffect,只触发name相关)
总结
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)}</>;
});
}
}
具体的示例可以查看:codesandbox.io/s/react-com…
基于这种方式,可以封装出可重用的业务组件:表单业务组件,表格业务组件等等,会极大程度的解放生产力!
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等等类型约束,纯函数组件没有限定
stackoverflow.com/questions/5…
import { MouseEvent } from 'react'
是什么意思?SyntheticEvent是什么类型?
import { MouseEvent } from 'react'
是什么意思?
好文章:fettblog.eu/typescript-…
- 用于事件类型约束
- 除了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。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!