Api
Fragment
- 创建一个文档碎片,不占用dom节点
- 基本上等同于
<></>
- Fragment可以添加key值,
<></>不可以
- 经过map遍历返回的元素,react会默认在最外层套上Fragment,在数据结构中可以这样识别,如果react虚拟dom外套了一层[],就是外面是一个虚拟dom
()=>{
return <React.Fragment></React.Fragment>
}
forwardRef
- 从父级或祖级别拿到子孙级的dom
- 使用原因:在父级组件传递ref属性名,子组件无法使用,react限制,会抛出异常
- 使用方法见下方
import React, { forwardRef } from 'react';
const Son = (props: any) => {
const { grandRef } = props
return (
<>
<button>我是孙页面</button>
<p ref={grandRef}>测试</p>
</>
)
};
const Father = (props: any) => {
return <div>
<Son grandRef={props.grandRef} />
</div>
};
const NewFather = forwardRef((props, ref) => <Father grandRef={ref} {...props} />)
const hook = () => {
let a: any = null;
console.log(a);
return (
<React.Fragment>
{/* <Son grandRef={(ref: any) => a = ref} /> */}
<NewFather ref={(node) => a = node} />
<button onClick={() => {
console.log(a);
}}>输出</button>
</React.Fragment>
)
}
export default hook;
memo
- 做性能优化使用
- react默认会对基本数据类型进行比较,如果前后值一样,则不触发更新子组件
- 引用数据类型是以空间地址进行比较,所以当引用类型地址更改,但内容没有更改,会触发视图更新
- memo第一个值传入组件,第二个值为函数
memo只能针对props,不能针对state
- 当第二个值为函数的时候,函数接收两个参数,oldProps,newProps,且函数返回true代表不更新子组件,为false则更新
const Son = (props: any) => {
console.log('子组件渲染');
return <>num:{props['obj']['num']}</>
};
const MemoSon = memo(Son, (oldProps, newProps) => oldProps['obj']['num'] === newProps['obj']['num']);
const App = () => {
const [obj, setObj] = useState<any>({ num: 0 });
const click = () => {
setObj({ num: 0 });
}
return (
<>
<button onClick={click}>触发一波</button>
<MemoSon obj={obj} />
</>
)
}
Suspense
- 做异步加载组件使用,需要配合lazy一起使用
- 有一个参数 fallback
- fallback的值需要是一个组件
- 通过子组件的状态决定渲染什么,可以实现最小级页面刷新,通常我们都是刷新状态,整个组件刷新,而Suspense只是更新自己的状态
- 可用于服务端流式渲染与懒加载
class Suspense extends React.Component {
mounted = null;
state = {loading: false};
componentDidCatch(error) {
if(typeof error.then === 'function') {
this.setState({loading: true});
error.then(() => {this.setState({loading: false})});
}
}
render() {
const { fallback, children } = this.props;
const { laoding } = this.state;
return loading ? fallback : children;
}
}
import React, { Suspense, useEffect, useRef } from "react";
import actionCreators from "../store/actionCreators/user";
function Users({resource}) {
let list = resource.read();
return (
<ul>
{list.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
function wrapPromise(promise) {
let status = "pending";
let result;
let suspender = promise.then(
(r) => {
status = "success";
result = r;
},
(e) => {
status = "error";
result = e;
}
);
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
},
};
}
function UserList() {
const dispatch = useDispatch();
const resourceRef = useRef();
if (!resourceRef.current) {
const promise = dispatch(actionCreators.getUserList());
const resource = wrapPromise(promise);
resourceRef.current = resource;
}
return (
<Suspense fallback={<div>加载中......</div>}>
<Users resource={resourceRef.current} />
</Suspense>
);
}
export default UserList;
lazy
- 异步加载组件使用,需要配合Suspense
- 参数为一个函数,函数需返回一个promise,promise的resolve为一个object,且这个对象内要有default,值为一个组件
- 打开控制台,可以看到,在加载成功LazyComponent的时候,浏览器又加载了一个js资源
import React, { Component,lazy,Suspense } from 'react';
import * as _ from './test';
const LazyComponent = lazy(() => new Promise((resolve: any) => {
setTimeout(() => {
resolve(_)
}, 2000)
}))
class index extends Component {
render() {
return(
<>
<Suspense fallback={<div>加载中展示的内容,在加载成功后会消失</div>} >
<LazyComponent />
</Suspense>
</>
)
}
}
Profiler
- 测试内部组件性能
- 接收两个参数:id 和 onRender,onRender是一个函数,当内部组件渲染完成就会执行
- 不会增加层级结构
import React,{Profiler} from 'react';
const App = () => {
return (
<Profiler
id="test"
onRender={(...arg) => {
console.log(arg);
/*
* {
* 0:id
* 1:"mount" || "update"
* }
*/
}}
>
<div>测测测</div>
</Profiler>
)
}
StrictMode
const App = () => {
return (
<StrictMode>
<div>测测测</div>
</StrictMode>
)
}
createElement
- 创建虚拟dom对象
- 使用babel解析jsx就是为了将jsx解析成虚拟dom对象,因为浏览器无法识别jsx
- createElement会在虚拟dom对象上添加$$typeof属性,值为Symbol(react.element)
- 三个参数
- 参数1:可以为组件或者字符串的标签名
- 参数2:传递给该组件或标签的参数
- 参数3:如果为字符串就是文本,如果为数组就是子集,里面可以继续嵌套createElement对象
import { createElement } from 'react';
const App = () => {
console.log(createElement('div', {},'测试内容'));
return (
createElement('div', {}, [
createElement('span', { className: "span", key: '1' }, '我是span'),
createElement('a', { id: 'a_id', href: './', key: '2' }, '我是a标签')
])
)
};
cloneElement
- 克隆组件
- 会先将组件转换为createElement对象,然后再合并
- 两个参数
- 参数1:组件
- 参数2:props参数,object类型
- 会将自身props与克隆组件的props进行合并,自身props优先级高
- 可以用来赋默认值和混入参数
const Test = (props: any) => {
return (
<>
num:{props['num']}
<br />
cloneProps:{props['cloneProps']}
</>
)
}
const App = () => {
return (
<>
<Test num={1}/>
<br />
{cloneElement(<Test num={1} />, { num: '能不能覆盖原有num', cloneProps: '我是克隆的时候传递的参数' })}
</>
)
}
createContext
- 创建上下文对象
- 接收一个object类型为参数,返回Provider和Consumer
- 使用的时候Provider在外层,Consumer在内层,在Consumer中可通过函数拿到传递过来的值
- Provider可设置多个,且都需要有value属性,Consumer找值是按照距离最近的Provider来找的,最近的Provider上的value会覆盖其他的value
const defaultValue: any = {};
const MyContext = createContext(defaultValue);
const TestRender = (props: any) => {
const { a, b } = props;
return (
<>
我拿到的值a:{a}
我拿到的值b:{b}
</>
)
}
const ComponentB = () => {
return (
<MyContext.Provider value={{ a: 3 }}>
<MyContext.Consumer>
{(value) => <TestRender {...value} />}
</MyContext.Consumer>
</MyContext.Provider>
)
}
const App = () => {
return (
<MyContext.Provider value={{ a: 1, b: 2 }}>
<ComponentB />
</MyContext.Provider>
)
}
isValidElement
- 测试是否为react虚拟dom
- 是的话返回true,反之则为false
- 文本不是
const TestOne = () => {
return <>测试</>
}
const App = () => {
console.log(isValidElement('121212'));
console.log(isValidElement(<TestOne />));
return (<></>)
}
ref部分
createRef
const App = () => {
const node: any = createRef();
const click = () => {
console.log(node);
};
return (
<>
<button onClick={click}>输出</button>
<div ref={node}></div>
</>
)
};
class App extends Component {
node: any = createRef();
click = () => {
console.log(this.node);
};
render(): React.ReactNode {
return (
<>
<button onClick={this.click}>输出</button>
<div ref={this.node}></div>
</>
)
}
}
ref语法糖
const App = () => {
let node: any;
const click = () => {
console.log(node);
};
return (
<>
<button onClick={click}>输出</button>
<div ref={(val)=>node=val}></div>
</>
)
}
interface App {
node: any
}
class App extends Component {
click = () => {
console.log(this.node);
};
render(): React.ReactNode {
return (
<>
<button onClick={this.click}>输出</button>
<div ref={(val) => this.node=val}></div>
</>
)
}
}
useRef
- hooks函数
- 作用1:当做ref元素
- 作用2:维护一个全局稳定可访问的值
- 更改的时候使用current进行更改
- 在useCallback等hook中,如果想实时访问其他值,需要在依赖里添加对应值,但是useRef可以不用这样
- 注意:
在useCallback这类函数依赖项发生变化的时候,可以拿到当前上下文中的所有值,而不是初始值,不要记混了
- 在函数组件中不要使用CreateRef:
- 在类组件中,CreateRef创建的ref实例会被放在实例上,所以不会丢失
- 而在函数组件中,CreateRef创建的ref实例放在函数内,每次数据刷新函数都会重新执行,所以可能会导致ref丢失
- useRef创建的ref实例会放在函数组件对应的fiber上,不会出现上面的情况,所以在函数组件中务必使用useRef
- useRef与CreateRef创建的ref对象一致,只是存储位置不同
const App = () => {
const node: any = useRef();
const val: any = useRef({ a: 1 });
const [testVal, setTestVal] = useState(1);
const click = () => {
console.log(node);
};
useEffect(() => {
setTestVal(2);
val.current = 2;
}, [])
const testCallback = useCallback(() => {
console.log(testVal);
console.log(val);
}, [])
return (
<>
<button onClick={click}>输出ref</button>
<button onClick={testCallback}>测试作用2</button>
<div ref={node}></div>
</>
)
}
Children相关
import React, { Component, isValidElement, Children } from 'react';
const Son = (props: { name: string, val?: string }) => {
const { name, val } = props;
return (
<>
<div>{name}</div>
<div>{val}</div>
</>
)
};
const Father = (props: any) => {
let { children } = props;
return Children.map(children, (item, index) => {
return item;
})
};
const R = () => {
return (
<Father>
<Son name="一号" val="值"></Son>
{() => <Son name="一号" val="值"></Son>}
{0 ? <div>1</div> : <div>2</div>}
{[1, 2, 3].map((item, index) => <div key={index}>{item}</div>)}
</Father>
)
}
Children.map||Children.forEach
- 保留原生的特性、map返回数组,forEach没有返回
- 对原生方法进行拓展:可以进行对children深层次的便利
- 使用方法
import React, { Children } from 'react';
const Test = (props: any) => {
const { children } = props;
console.log(children);
return (
<div>
{Children.map(children, (item: any) => {
if (item['type'] === 'div') {
return item
}
})}
</div>
)
}
const App = () => {
return (
<Test>
{[1, 2, 3].map((item, index) => {
return <div key={index}>item</div>
})}
<span>hello,world</span>
</Test>
)
}
Children.count
- 返回组件的总数,可以理解为map函数的回调执行次数
- 参数只有1个,children
console.log(Children.count(children));
Children.toArray
- 将children进行扁平化
- 参数:一个,children
- 要保证传入的children是一个数组,不接受单个的react虚拟dom对象
console.log(Children.toArray(children));
Children.only
- 如果扁平化后的子集只有一个,那么返回它,否则抛出异常
Children.only(children);
Children.only(children);
hooks
useState
- 接收一个值,值可以为任意数据类型
- 值如果为函数的话,函数的返回结果就是初始值
- 返回一个元组,第一项为值,第二项为修改值的方法,使用第二项修改才会触发页面渲染
- 值为引用数据类型的话,后续修改不改变空间地址不触发渲染
- 同一上下文下,进行多次set操作,页面只会执行最后一次
- 以下方更新函数举例,setCount可以传值,也可以传函数,函数的
第一个参数为当前上下文中前一次set的值,是实时的,函数的return值为将要set的值
const App = () => {
const [count, setCount] = useState<number>(0);
return (
<>
<button onClick={setCount.bind(null, count + 1)}>+1</button>
<div>{count}</div>
</>
)
}
useEffect
- 模拟生命周期
- 两个参数:
- 函数,函数的return值可为一个函数,在页面销毁时执行
- 依赖项,可不传,也可为数组,数组内值改变,函数执行
- 依赖项不传的话,每次组件渲染都会执行函数
- 在函数中异步调用set方法会导致内存泄漏,解决方法为在return的函数中进行清除
- 会在dom渲染完成后执行,执行顺序:
组件更新->浏览器绘制dom完成->执行useEffect
const App = () => {
const [count, setCount] = useState<number>(0);
useEffect(() => {
console.log(2);
return () => {
}
}, []);
console.log(1);
return <></>
}
useLayoutEffect
- 与useEffect基本一致,但执行顺序不同
组件更新->执行useLayoutEffect->浏览器绘制dom完成
- 会在一定程度上阻塞浏览器绘制
useMemo
- 计算属性,当依赖值发生改变会重新计算返回值,避免不必要的判断
- 两个参数
- 函数,函数的返回值就是useMemo的返回值
可为dom,也可为数据
- 依赖值
const App = () => {
const [count, setCount] = useState<number>(0);
const [countTwo, setCountTwo] = useState<number>(0);
const Son = useMemo(() => {
return () => <div>{count}</div>
}, [countTwo])
return (
<>
<button onClick={setCount.bind(null, count + 1)}>+1</button>
<button onClick={setCountTwo.bind(null, countTwo + 1)}>countTwo+1</button>
<Son></Son>
</>
)
};
useCallback
- 基本上等同于useMemo,区别在于,useMemo返回是函数的计算值,而useCallback返回的是当前函数
- 参数也一致
useReducer
- 是简化的reducer,强化的useState
- 接收两个参数
- 函数
- 函数有两个值,第一个是state,第二个是acthon,后续修改state的时候会根据action来操作,函数的return值为后续更新的值
- 默认值
- 返回一个元组,第一项为值,第二项为修改函数
const App = () => {
const [number, dispatchNumbner] = useReducer((state: any, action: any) => {
const { payload, name } = action
switch (name) {
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
}
return state
}, 0)
return <div>
当前值:{number}
{ /* 派发更新 */}
<button onClick={() => dispatchNumbner({ name: 'add' })} >增加</button>
<button onClick={() => dispatchNumbner({ name: 'sub' })} >减少</button>
<button onClick={() => dispatchNumbner({ name: 'reset', payload: 666 })} >赋值</button>
<div>{number}</div>
</div>
}
useContext
- 获取最近的Provider的value
- 一个参数,createContext实例
- 相对Context.Consumer的书写方式,会更好看些
const Context = createContext({ a: 0, b: 0 });
const SonOne = () => {
const val = useContext(Context);
return <div>{val['a']}</div>
};
const SonTwo = () => {
return (
<Context.Consumer>
{val => <div>{val['a']}</div>}
</Context.Consumer>
)
};
const App = () => {
return (
<Context.Provider value={{ a: 1, b: 1 }}>
<SonOne />
<SonTwo />
</Context.Provider>
)
}
useImperativeHandle
- 用来父级操作子级组件或
拿到子级组件的一些方法
- 通过ref.current拿到子组件useImperativeHandle中的返回值
- 子组件在props后还需增加一个ref参数
- 需配合forwardRef使用
- 三个参数
const Son = (props: any, ref: any) => {
const inputRef = useRef<any>();
const [val, setVal] = useState<string>('');
const [flag, setFlag] = useState<boolean>(true);
useImperativeHandle(ref, () => {
const handleRefs = {
onFocus() {
inputRef.current.focus();
},
onChangeValue(value: string) {
setVal(value)
},
log: () => {
console.log(val, '值');
}
}
return handleRefs
}, [val])
return (<input type="text" value={val} onChange={() => { }} ref={inputRef} placeholder="默认值" />)
};
const ForwarSon = forwardRef(Son)
const App = () => {
const ref = useRef<any>();
const handerClick = () => {
const { onFocus, onChangeValue } = ref.current;
onFocus()
onChangeValue('let us learn React!')
};
const logVal = () => {
ref.current.log();
}
return (
<Fragment>
<button onClick={handerClick}>触发dom操作,赋值</button>
<br />
<button onClick={logVal}>输出值</button>
<br />
<ForwarSon ref={ref} />
</Fragment>
)
};
react-dom的api
createPortal
- 用来创建一个指定位置的虚拟dom对象
- 两个参数
- 返回的是一个虚拟dom对象,需要使用,但位置已经指定好了
import { createPortal } from 'react-dom';
function WrapComponent({ children }: any) {
const domRef = useRef(null)
const [PortalComponent, setPortalComponent] = useState(null)
useEffect(() => {
setPortalComponent(createPortal(children, domRef.current))
}, [])
return <div>
<div className="container" ref={domRef} ></div>
{PortalComponent}
</div>
}
class Index extends React.Component {
render() {
return (
<div>
<WrapComponent>
<div>hello,world</div>
</WrapComponent>
</div>
)
}
}
unstable_batchedUpdates
- react默认会对同一上下文的多次set进行合并,避免进行重复渲染的情况
- 但是在一些情况下,会导致默认处理失效,如类组件的异步事件中
函数组件复现不出来
- 可使用unstable_batchedUpdates进行规避重复渲染
interface Index {
state: any
}
class Index extends React.Component {
constructor(props: any) {
super(props)
this.state = {
numer: 1,
}
}
handerClick = () => {
Promise.resolve().then(() => {
ReactDOM.unstable_batchedUpdates(() => {
this.setState({ numer: this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer: this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer: this.state.numer + 1 })
console.log(this.state.numer)
})
})
}
render() {
return <div>
<button onClick={this.handerClick}>click me</button>
</div>
}
}
flushSync
- 调整set优先级,会立即执行
- 一个参数
- 函数,函数会立即执行,所以内部的set也就是优先级最高,会立即渲染一次页面
class Index extends React.Component {
state = { number: 0 }
handerClick = () => {
setTimeout(() => {
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(() => {
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render() {
const { number } = this.state
console.log(number)
return (
<>
<div>{number}</div>
<button onClick={this.handerClick} >测试flushSync</button>
</>
)
}
}