规范写法;合理处理细节问题
在项目开发中,稍微注意下,有时就能避免不必要的开销,达到优化的目的。
1. 绑定事件尽量不要使用箭头函数
面临问题
react的更新主要来自于props改变的被动渲染,和state改变的主动渲染,当这两个条件都未改变时,如果我们给组件绑定箭头函数的事件,这会导致该组件每次都会被 render,即便是 PureComponent
<User onDelUser={() => handleDelUser(user.id)} />
当传递给 User 组件的 props 没有任何变动时,组件不会重新渲染。但是由于传递的是匿名箭头函数,所以父组件每次 render 的时候都会去重新定义一个新的函数传递给 User 组件,其 props 在父组件的每次重新渲染中都会变动,也就导致了不必要的渲染开销。
2. 循环正确使用key
为什么使用key?
react利用key来识别组件,它是一种身份标识,相同的key,react认为是同一个组件,这样后续相同的key对应组件都不会被创建
有了key属性后,就可以与组件建立了一种对应关系,react根据key来决定是销毁重新创建组件还是更新组件。
错误用法
- 用
index作为key。 - 用
index拼接其他字段作为key;如果有元素移动或者删除,那么就失去了一一对应关系,剩下的节点都不能有效复用。
正确用法
使用唯一 id 作为key,能够做到有效复用元素节点。
3. 结合 React.Suspense 和 lazy, 动态加载组件
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
常用api解读
主要是开发中一些常用的api,目前 React 开发 Hooks 的使用十分普遍,本文业主要围绕Hooks的使用心得,和在开发中的实践来表述。
Hook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。
实际上react-hooks就是函数组件解决没有state,生命周期,逻辑不能复用的一种技术方案。当下的 react 开发中,hooks 的使用已然成为一种趋势。
React.Fragment
react中不允许一个组件返回多个节点元素,需要有一个公共的父级元素才可以。
return (
<div>
<a>1</a>
<a>2</a>
<a>3</a>
</div>
)
但是很多时候我们不希望出现一个无意义的节点,就可以使用Fragment来代替,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
可以使用<></>短语法来简写,但如果是遍历的元素,只有Fragment可以支持key属性,简写方式不支持。
key是唯一可以传递给Fragment的属性。
React.StrictMode
StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告并输出在控制台中。
严格模式检查仅在开发模式下运行;不会影响生产构建。可以为应用程序的任何部分启用严格模式,如果要全局开启,可以在App或者Router外包裹
import React from 'react';
function ExampleApplication() {
return (
<>
<Header />
<React.StrictMode>
<ComponentOne />
<ComponentTwo />
</React.StrictMode>
<Footer />
</>
);
}
在上述的示例中,不会对 Header 和 Footer 组件运行严格模式检查。但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查。
StrictMode 目前有助于:
createElement
React 并不强制要求使用 JSX。当你不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。
每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。
JSX作为语法糖,可以让我们编写的代码更为简洁。
用 JSX 编写的代码:
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
不使用 JSX 的代码:
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
forwardRef 和 useImperativeHandle
React.forwardRef 会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle应当与forwardRef 一起使用。
const FilterTemplate = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
return {
showModal() {
setIsOpen(true)
},
hideModal() {
setIsOpen(false)
},
}
})
})
const Index = () => {
const filterTemplateRef = useRef(null)
cosnt handleOpenModal = () => {
filterTemplateRef.current?.showModal();
}
return (
<>
<FilterTemplate ref={filterTemplateRef} />
<Button onClick={handleOpenModal}>打开Modal</Button>
</>
)
}
React.memo
React.memo和PureComponent作用类似,可以用作性能优化,React.memo 是高阶组件,函数组件和类组件都可以使用, 与PureComponent的区别是 React.memo只能对props的情况确定是否渲染,而PureComponent是针对props和state做浅层比较。
React.memo仅检查 props 变更。如果函数组件被React.memo包裹,且其实现中拥有useStateuseReducer或useContext的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
memo接受两个参数:
- 被包裹的组件本身,
- 自定义的比较函数。该函数若返回
true组件不渲染 , 返回false组件重新渲染。
function MyComponent(props) {
/* 使用 props 渲染 */
}
const areEqual = (prevProps, nextProps) => {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
useRef
- 用于访问DOM refs
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- 它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。 「ref」 对象是一个 current 属性可变且可以容纳任意值的通用容器,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
const uuid = useRef('');
uuid.current = res.uuid;
// 可规避状态写在依赖数组里导致一直刷新
const [count, setCount] = useState(0);
const countRef = useRef();
countRef.current = count;
useEffect(() => {
const id = setInterval(() => {
console.log(countRef.current);
}, 1000);
return () => clearInterval(id);
}, []);
// 这样,count 的确不再被使用,而是用ref存储了一个在所有帧中共享的变量。
- 关于回调ref 如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现,可以参照这个官方示例。
useMemo
返回函数的返回值
useMemo接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,作为dep依赖项,数组里面的依赖项发生变化,重新执行第一个函数,产生新的值。
应用场景: 缓存一些值,避免重新执行上下文
const number = useMemo(()=>{
/** ....大量的逻辑运算 **/
return number
// 只有 props.number 改变的时候,重新计算number的值。
},[ props.number ])
useCallBack
缓存函数
hooks对「有没有变化」这个点其实很敏感。如果一个effect内部使用了某数据或者方法。若我们依赖项不加上它,那很容易由于闭包问题,导致数据或方法,都不是我们理想中的那个它。如果我们加上它,很可能又会由于他们的变动,导致effect疯狂的执行。
一、 在组件内部,那些会成为其他useEffect依赖项的方法,建议用 useCallback包裹,或者直接编写在引用它的useEffect中。
二、 己所不欲勿施于人,如果你的function会作为props传递给子组件,请一定要使用 useCallback包裹,对于子组件来说,如果每次render都会导致你传递的函数发生变化,可能会对它造成非常大的困扰。同时也不利于react做渲染优化。
举例说明 在现代码点这里
父组件
export default function App() {
const [number, setNumber] = useState(1);
// 不会触发子组件更新
const getInfo = useCallback((sonName) => {
console.log(sonName);
}, []);
// 会触发子组件更新
const getInfoNoCallback = (sonName) => {
console.log(sonName);
};
return (
<>
<Counter getInfo={getInfo} />
<h1>{number}</h1>
{/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
<button onClick={() => setNumber(number + 1)}>增加</button>
</>
);
}
子组件
useState
这是最常用的一个Hook了,使用方法就不再赘述了。这里说一下如何实现类组件的一些实用方法。
实现this.setState({}, () => {})第二个参数
- 类组件中封装
// 定义
setStateSync(state) {
return new Promise(resolve => {
this.setState(state, () => {
resolve();
});
});
}
// 使用
const test = async () => {
await this.setStateSync({})
// 此处的state已经是实时的了
}
- 主要看下函数组件通过自定义hook模拟
这样我们使用useState的时候就不用操心取值时异步的问题啦。
function useCallbackState(state: any) {
const cbRef = useRef<any>();
const [data, setData] = useState(state);
useEffect(() => {
cbRef.current && cbRef.current(data);
}, [data]);
return [
data,
function (val: any, callback: any) {
// 把第二个参数的函数保存到ref中,setData后执行,你的函数拿到的就是最新的值啦
cbRef.current = callback;
setData(val);
}
];
}
// 使用
const [num, setNum] = useCallbackState(0);
setNum(2, (data) => {
console.log('num', data)
})