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的节点下