React

145 阅读12分钟

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']}</>
};
// 当新旧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只是更新自己的状态
  • 可用于服务端流式渲染与懒加载
//Suspense源码
class Suspense extends React.Component {
    mounted = null;
    state = {loading: false};
    componentDidCatch(error) {
        if(typeof error.then === 'function') {
            this.setState({loading: true});//先渲染fallback
            error.then(() => {this.setState({loading: false})});
        }
    }
    
    render() {
        const { fallback, children } = this.props;
        const { laoding } = this.state;
        return loading ? fallback : children;
    }
}
  • 配合异步任务使用:接口请求demo
import React, { Suspense, useEffect, useRef } from "react";
import actionCreators from "../store/actionCreators/user";//redux的派发,可忽略
function Users({resource}) {
  let list = resource.read();
  return (
    <ul>
      {list.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
//失败或pending将promise状态上报,否则直接返回
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 * as _ from './test'来充当{};
    也可以使用{default:()=><Test/>}
    setTimeOut是模仿异步
*/
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', {},'测试内容'));
    /*
    {
        虚拟dom对象
        $$typeof: Symbol(react.element)
        key: null
        props: {children: '测试内容'}
        ref: null
        type: "div"
        _owner: FiberNode {tag: 0, key: null, stateNode: null, elementType: ƒ, type: ƒ, …}
        _store: {validated: false}
        _self: null
    }
    */
    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: '我是克隆的时候传递的参数' })}
        </>
    )

}
/*
结果
num:1
cloneProps:

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;
    //a最后结果为3 b为null
    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'));//false
    console.log(isValidElement(<TestOne />));//true

    return (<></>)
}

ref部分

createRef

  • 创建一个ref元素
//在函数组件中使用
const App = () => {
    const node: any = createRef();
    const click = () => {
        console.log(node);
        // {current:div对象}
    };
    return (
        <>
            <button onClick={click}>输出</button>
            <div ref={node}></div>
        </>
    )
};

//在类组件中使用
class App extends Component {
    node: any = createRef();
    click = () => {
        console.log(this.node);
        // {current:div对象}
    };
    render(): React.ReactNode {
        return (
            <>
                <button onClick={this.click}>输出</button>
                <div ref={this.node}></div>
            </>
        )
    }
}

ref语法糖

  • react提供的一种语法糖
// 函数中使用
const App = () => {
    let node: any;
    const click = () => {
        console.log(node);
        // {current:div对象}
    };
    return (
        <>
            <button onClick={click}>输出</button>
            <div ref={(val)=>node=val}></div>
        </>
    )
}
// 类中使用
interface App {
    node: any
}
class App extends Component {
    click = () => {
        console.log(this.node);
        // {current:div对象}
    };
    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);
        // {current:div对象}
    };
    // 更新一次数据
    useEffect(() => {
        setTestVal(2);
        val.current = 2;
    }, [])
    
    const testCallback = useCallback(() => {
        console.log(testVal);// 1
        console.log(val);//{current:2}
    }, [])
    return (
        <>

            <button onClick={click}>输出ref</button>
            <button onClick={testCallback}>测试作用2</button>
            <div ref={node}></div>
        </>
    )
}

Children相关

  • 如果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;
    //     children = props.children.map((item: any) => {
    //     if (isValidElement(item)) return item;
    //     if (typeof item === 'function') return item();
    //     return null;
    // });
    
    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>
    )
}

//{() => <Son name="一号" val="值"></Son>} 没有被渲染出来
//把注释部分去掉,处理下children,然后再渲染即可显示

Children.map||Children.forEach

  • 保留原生的特性、map返回数组,forEach没有返回
  • 对原生方法进行拓展:可以进行对children深层次的便利
  • 使用方法
    • children
    • 函数
// 上面说到,fargment包裹的dom会是一个数组,所以直接使用map会无法便利子集
// 使用react的Children上的map可以便利到深层,下面例子中对[]中的数组进行了深层便利,这是原生的map无法实现的
import React, { Children } from 'react';
const Test = (props: any) => {
    const { children } = props;
    /*
    [
        0: (3) [{…}, {…}, {…}]
        1: {$$typeof: Symbol(react.element), type: 'span', key: null, ref: null, props: {…}, …}
        length: 2
    ]
    */
    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
// children代表上面的children,不写重复代码了
// 上面例子输出
 console.log(Children.count(children));//4 也就是[]的3次,span的一次,总和为4次

Children.toArray

  • 将children进行扁平化
  • 参数:一个,children
  • 要保证传入的children是一个数组,不接受单个的react虚拟dom对象
console.log(Children.toArray(children));
//[{type:'div'},{type:'div'},{type:'div'},{type:'span'}]

Children.only

  • 如果扁平化后的子集只有一个,那么返回它,否则抛出异常
// [[{type:'div'},{type:'div'},{type:'div'}]]
 Children.only(children);//上面也是一个,但是扁平化后为3个,所以抛出异常
//  [{type:'div'}]
 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
// 1 2
const App = () => {
    const [count, setCount] = useState<number>(0);
    useEffect(() => {
        // 初始执行一次,依赖值发生改变也会执行
        console.log(2);
        return () => {
            // 函数销毁执行
        }
    }, []);
    console.log(1);
    return <></>
}

useLayoutEffect

  • 与useEffect基本一致,但执行顺序不同
  • 组件更新->执行useLayoutEffect->浏览器绘制dom完成
  • 会在一定程度上阻塞浏览器绘制

useMemo

  • 计算属性,当依赖值发生改变会重新计算返回值,避免不必要的判断
  • 两个参数
    • 函数,函数的返回值就是useMemo的返回值可为dom,也可为数据
    • 依赖值
// 只有countTwo+1执行,Son才会重新计算
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 = () => {
    /* number为更新后的state值,  dispatchNumbner 为当前的派发函数 */
    const [number, dispatchNumbner] = useReducer((state: any, action: any) => {
        const { payload, name } = action
        /* return的值为新的state */
        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 = () => {
    /* number为更新后的state值,  dispatchNumbner 为当前的派发函数 */
    return (
        <Context.Provider value={{ a: 1, b: 1 }}>
            <SonOne />
            <SonTwo />
        </Context.Provider>
    )
}

useImperativeHandle

  • 用来父级操作子级组件或拿到子级组件的一些方法
  • 通过ref.current拿到子组件useImperativeHandle中的返回值
  • 子组件在props后还需增加一个ref参数
  • 需配合forwardRef使用
  • 三个参数
    • ref
    • 函数
    • 依赖项
const Son = (props: any, ref: any) => {
    const inputRef = useRef<any>();
    const [val, setVal] = useState<string>('');
    const [flag, setFlag] = useState<boolean>(true);
    // 依赖项去掉的话就拿不到val,一直是函数第一次执行上下文中的值,undefined
    // 加上依赖项,在当前函数内有延迟,延迟一次,但可以拿到

    useImperativeHandle(ref, () => {
        const handleRefs = {
            /* 声明方法用于聚焦input框 */
            onFocus() {
                inputRef.current.focus();
            },
            /* 声明方法用于改变input的值 */
            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节点
  • 返回的是一个虚拟dom对象,需要使用,但位置已经指定好了
// hello,world展示在.container中
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 = () => {
    //     // 1 1 1事件合并
    //     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)
    // }

    // handerClick = () => {
    //     // 异步阻碍事件合并,2 3 4 渲染三次
    //     Promise.resolve().then(() => {
    //         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)
    //     })
    // }
    handerClick = () => {
        // 使用unstable_batchedUpdates进行干预, 1 1 1 渲染一次
        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
        // 0是初始值
        // 通过flushSync将3的优先级调到最高,然后执行3
        // 2和4是正常set,选取后者进行合并,所以是4
        // 1是放在异步中,所以最后执行1
        console.log(number) // 0 3 4 1
        return (
            <>
                <div>{number}</div>
                <button onClick={this.handerClick} >测试flushSync</button>
            </>
        )
    }
}