even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、react组件的封装

在进行react组件封装的时候,如果需要实现类似vue里的slot的功能,可以通过props.children来实现,通常来讲,props.children接收到的类型分成三种,String, Array, Object, undefined, 如果做具名插槽就需要做进一步的判断,所以在封装组件的时候,建议使用主要部份使用props.children实现插槽,而部份的使用props的传值功能实现

如果插槽里只有一句话:String,

只有一个dom元素: Object(虚拟dom类型)

多个元素:Array

没有任何元素: undefined

import React, {Component} from 'react';
import ReactDom from 'react-dom'

class Index extends Component {
    render() {
        let childFoot = <div>这个是通过props传值实现的</div>;
        return (
            <div>
                <h2>这个是index里的内容</h2>
                <Item foot={childFoot}>  //foot部份是实现了props传值, 其他的是用children实现的
                    <h3>这个是子组件的标题</h3>
                    <div>这个是子组件的内容部份</div>
                </Item>
            </div>
        );
    }
}

const Item = props => {
    //可以通过<></>实现判空渲染
    console.log(props.foot)
    return <div>
        {props.children || '这个是默认元素'}
        {props.foot? <footer>{props.foot}</footer>: <></>}
    </div>
}

ReactDom.render(<Index/>, window.root)

注意:如果需要实现如vue里的具名插槽,那么就需要对props.children进行遍历,并且判断每个元素的props是否带有自定义的标识,如slot=‘header’,并且对数据进行加工处理

 2、react中memo, useMemo, useCallBack性能优化

React.memo为高阶组件, 它与React.PureComponent非常相似,但它适用于函数组件, 但不适用于class组件

React.memo接收两个参数,一个参数是函数式组件,第二个参数是比对函数,并且这个比对函数接收两个参数prevProps, nextProps分别是上次的所有props集合以及本次的props集合,并且返回一个布尔类型的值,如果为true,则函数式组件不重新渲染,否则会重新渲染,如果第二个比对函数不传,系统会默认比对所有的参数,一发现有变化则重新渲染

import React, {memo, useState} from 'react';
import ReactDom from 'react-dom'

const WrapItem = memo(props => { //函数式组件
    console.log('item渲染了一次')
    return <div>
        <h4>这个是props {props.name}</h4>
    </div>
}, (prevVal, nextVal) => {      //比对函数,改变数值对于该组件没有影响,所以不做判断,但是名称有影响,返回的是一个布尔类型的值
    return prevVal.name === nextVal.name
})

const App = () => {
    let [count, setCount] = useState(0)
    let [name, setName] = useState('yfBill')
    const add = () => {
        setCount(++count)   //改变数值
    }
    const change = () => {
        setName('haha') //改变名称
    }
    return <div>
        <h3>这个是app组件{count}</h3>
        <button onClick={add}>数值+</button>
        <button onClick={change}>改变名称</button>
        <WrapItem name={name} count={count}/>
    </div>
}

ReactDom.render(<App/>, window.root)

 useMemo函数

  React.memo()是判断一个函数组件的渲染是否重复执行。

  useMemo()是定义一段函数逻辑是否重复执行。需要在memo的前提下执行, 使用方法useMemo(() => {return 数据}, [依赖])

import React, {memo, useMemo, useState} from 'react';
import ReactDom from 'react-dom'

const Item = memo(({info = 0}) => {
    console.log('item render')
    return <div>
        <h4>这个是子组件的数值---{info}</h4>
    </div>
})

const App = () => {
    let [count, setCount] = useState(0)
    let obj = useMemo(() => {      //只有符合条件后,item组件才会被渲染一次
        if(count > 2) return count * 2;
    }, [count])
    const add = () => {
        setCount(++count)
    }
    return <div>
        <h3>这个是父组件---{count}</h3>
        <button onClick={add}>改变count</button>
        <Item info={obj}/>
    </div>
}

ReactDom.render(<App/>, window.root)

 注意: useMemo()是需要有返回值的,并且返回值是直接参与渲染,因此useMemo()是在渲染期间完成的。

 例子二

import React, {memo, useMemo, useState} from 'react';
import ReactDom from 'react-dom'

const Item = memo(({info = []}) => {
    console.log('item render')
    return <div>
        <h4>这个是子组件</h4>
        {info.map((val, ind) => <span key={"item" + ind}>{val}</span>)}
    </div>
})

const App = () => {
    let [count, setCount] = useState(0)
    let [list, setList] = useState([])
// useMemo的关注点在于返回的结果,如果依赖的变量没有变化,那么obj所指向的对象不会变化 let obj
= useMemo(() => { let arr = list.filter(val => val % 2 === 0) if(arr.length > 3) return arr //也就是当条件满足的时候,才会去重新渲染子组件,否则不会渲染子组件,以达到节省性能的效果 }, [list]) const add = () => { setCount(++count) setList(list.concat(count)) } return <div> <h3>这个是父组件---{count}</h3> <button onClick={add}>改变count</button> <Item info={obj}/> </div> } ReactDom.render(<App/>, window.root)

 useCallback函数

import {
  FC,
  MutableRefObject,
  ReactElement,
  memo,
  useCallback,
  useRef,
  useState,
} from 'react';

interface IItemProps {
  count: MutableRefObject<number>;
  changeCount: (num: number) => void;
}
// 如果该组件需要进行依赖数据的展示更新,那么父组件就不需要使用useRef, 并且useCallback依赖项需要改成count
const Item: FC<IItemProps> = memo((props: IItemProps): ReactElement => {
  const { changeCount } = props;

  console.log('item render');
  return (
    <div>
      <button onClick={() => changeCount(1)}>item +1</button>
      <button onClick={() => changeCount(-1)}>item -1</button>
    </div>
  );
});

const App: FC = (): ReactElement => {
  const [count, setCount] = useState<number>(0);

  const currentCount = useRef<number>(count);

  // 注意:useCallback并不是节省的当前的组件的性能,而是节省的子组件的性能,
  // 因为不管是否使用useCallback,那么里面的函数都将被定义
  // 但是对于使用useCallback后,子组件在useCallback依赖数据未改变的时候始终指向的是原来的function
  // 如果子组件没有对依赖数据进行使用,那么可以使用useRef进行辅助
  const changeCount = useCallback((num: number) => {
    // setCount((preCount) => {
    //   return preCount + num;
    // });
    currentCount.current = currentCount.current + num;
    setCount(currentCount.current);
  }, []);

  return (
    <div>
      <div>count: {count}</div>
      <button onClick={() => changeCount(1)}>+1</button>
      <button onClick={() => changeCount(-1)}>-1</button>
      <Item changeCount={changeCount} count={currentCount} />
    </div>
  );
};

export default App;

注意:useCallback(fn) =  useMemo(() => fn)

memo, useMemo,  useCallback三者是搭配使用的, 这个只是相当于在函数式组件中使用,如果在类组件函数中使用,那么就需要用PureComponent实现该功能, 但是PureComponent对于对象只会进行浅层比较

 3、useEffect的使用

useEffect(回调函数) 这个回调函数会在初次加载完成和更新完成后触发,说白了就是类组件的componentDidMount与componentDidUpdate的合体

useEffect(回调函数,[]) 相当于componentDidMount只会被调用一次,注意:这里的第二个参数是空数组

useEffect(回调函数,[依赖]) 当依赖变化的时候,就会触发当前函数执行, 如果省略第二个参数,那么函数被render几次,那么该函数就会被执行几次

useEffect(回调函数,[]),当回调函数返回一个function时,并且第二个参数是【】空数组表示销毁时调用,相当于类组件的componentWillUnmount的效果

注意:该方法是在函数式组件中使用

import React, {useEffect, useState} from 'react';
import ReactDom from 'react-dom'

const Item = ({count}) => {
    // useEffect(() => {       //如果不配置第二个参数时,则创建和更新时都会被触发
    //     console.log('更新了')
    // })
    // useEffect(() => {      //如果第二个参数为空数组时,相当于类组件的componentDidMount只会执行一次
    //     console.log('更新了')
    // }, [])
    // useEffect(() => {      //如果第二个参数有传入依赖时,当依赖有变化,那么就会触发该函数的变化
    //     console.log('更新了')
    // }, [count])
    useEffect(() => {   //如果第一个参数有返回一个函数,并且第二个参数返回的是一个空数组,那么相当于类组件的ComponentWillUnmount
        console.log('更新了')
        return () => {
            console.log('yes')
        }
    }, [])
    return <div>this is item --- {count}</div>
}

const App = () => {
    let [count, setCount] = useState(0)
    let [flag, setFlag] = useState(true)
    return <div>
        <h2>this is App --- {count}</h2>
        {flag? <Item count={count}/>: null}
        <button onClick={() => {setCount(count => ++ count)}}>++</button>
        <button onClick={() => {setCount(count => --count)}}>--</button>
        <button onClick={() => {setFlag(() => false)}}>hide</button>
    </div>
}

ReactDom.render(<App/>, window.root)

注意:为了方便维护,一个组件中通常会有多个useEffect,react会按照顺序依次执行

4、useReducer函数的使用

当使用的变量比较多的时候,建议可以使用useReducer来替代useState

使用方法:

let [状态, 派发函数] = useReducer(reducer函数, 初始值(一般都是对象), 初始化函数)

派发函数是用来修改state的;通过让reducer执行的方式来修改state

在reducer函数里, react会使用函数的返回结果来替代之前的初始值

import React, {useReducer} from 'react';
import ReactDom from 'react-dom'
const App = () => {
    let initState = {name: 'yfbill', count: 0}      //初始值
    const init = () => {                            //reducer的第三个参数
        return {...initState}
    }
    const reducer = (state, config) => {  //接收两个值,一个是当前的state一个是传入的config配置
        if(config.type === 'count') {
            return {
                ...state,
                count: state.count + config.step
            }
        } else if(config.type === 'name') {
            return {
                ...state,
                name: config.target
            }
        } else if (config.type === 'reset') {
            return init()
        }
    }

    let [state, dispatch] = useReducer(reducer, initState, init)  //第一个参数是执行函数, 第二个参数是初始值, 第三个参数是重置数据的函数
    return <div>
        <h2>this is App</h2>
        <h3>name---{state.name}</h3>
        <h3>count---{state.count}</h3>
        <button onClick={() => {            //改变count的函数
            dispatch({type: 'count', step: 1})
        }}>count++</button>
        
        <button onClick={() => {           //改变name的函数
            dispatch({type: 'name', target: 'aaaa'})
        }}>change name</button>
        
        <button onClick={() => {          //重置数据的按钮
            dispatch({type: 'reset'})
        }}>重置</button>
    </div>
}

ReactDom.render(<App/>, window.root)

 5、 createPortal的使用

通常来讲,createPortal可以指定元素的挂载节点,相当于vue里的 teleport

使用举例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <title>React App</title>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <div id="App"></div>
</body>

</html>

js部份如下

import { PureComponent, ReactElement, RefObject, createRef } from 'react';
import { createPortal } from 'react-dom';

class App extends PureComponent {
  public render(): ReactElement {
    return (
      <div>
        <div id="title"></div>
        <div>this is first content</div>
        {createPortal(
          <h1>this is App</h1>,
          document.getElementById('App') as Element,
        )}
      </div>
    );
  }
}

export default App;

这个时候createPortal部份的元素会被挂载到id为App的节点下

 

posted on 2021-04-04 01:11  even_blogs  阅读(919)  评论(0编辑  收藏  举报