- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章如何编写高性能的 React 代码:规则、模式、注意事项由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
首先会以常规的模式来实现组件,然后再考虑性能的情况下重构每个步骤,并从每个步骤中提取一个通用规则,这些规则可以应用于大多数应用程序。然后比较最后的结果.
下面将编写一个“国家设置”页面,用户可以从列表中选择国家,查看该国家的信息,可以保存国家:
可以看到,左侧有一个国家列表,带有“已保存”和“已选择”状态,当点击列表中的选项时,右侧会显示该国家的详细信息。当按下保存按钮时,选定的国家会变成已保存,已保存的选项的背景会变成蓝色.
首先,根据设计图,我们要考虑应用的结构以及要实现哪些组件:
页面组件:在其中处理提交逻辑和国家选择逻辑,
国家列表组件:将呈现列表中的所有国家,并进行过滤和排序等操作,
国家选项组件:将所有国家呈现在国家列表组件中,
选定国家组件:将呈现选定国家的详细信息,并具有保存按钮.
当然,这不是实现这个页面的唯一方式,实现方式仅供参考。下面就来看看如何实现这些组件.
下面终于要开始写代码了,下面就从根开始,实现Page组件,步骤如下:
export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); return ( <> <h1>Country settings h1> <div css={contentCss}> <CountriesList countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> <SelectedCountry country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> div> ); };
在 React 中,当组件的 state 或者 props 发生变化时,组件会重新渲染。在 Page 组件中,当 setSelectedCountry 或者 setSavedCountry 被调用时,组件就会重新渲染。当组件的国家列表数据(props)发生变化时,组件也会重新渲染。CountriesList 和 SelectedCountry 组件也是如此,当它们的 props 发生变化时,都会重新渲染.
我们知道,React 会对 props 进行严格相等的比较,并且内联函数每次都会创建新值。这就导致了一个错误的的观念:为了减少 CountriesList 和SelectedCountry 组件的重新渲染,需要通过在 useCallback 中包装内联函数来避免在每次渲染中重新创建内联函数.
export const Page = ({ countries }: { countries: Country[] }) => { const onCountryChanged = useCallback((c) => setSelectedCountry(c), []); const onCountrySaved = useCallback(() => setSavedCountry(selectedCountry), []); return ( <> ... <CountriesList onCountryChanged={onCountryChange} /> <SelectedCountry onCountrySaved={onCountrySaved} /> ... ); };
而实际上,这样并不会起作用。因为它没有考虑到:如果父组件 Page 被重新渲染,子组件 CountriesList 也总是会重新渲染,即使它根本没有任何 props.
可以这样来简化 Page 组件:
const CountriesList = () => { console.log("Re-render!!!!!"); return <div>countries list, always re-renders div>; }; export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); return ( <> <h1>Country settings h1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> <CountriesList /> ); };
当每次点击按钮时,即使没有任何 props,都会看到 CountriesList 组件被重新渲染。由此,总结出第一条规则:「如果想把 props 中的内联函数提取到 useCallback 中,以此来避免子组件的重新渲染,请不要这样做,它不起作用。」 。
现在,有几种方法可以处理上述情况,最简单的一种就是使用 useMemo,它本质上就是缓存传递给它的函数的结果。并且仅在 useMemo 的依赖项发生变化时才会重新执行。这就就将 CountriesList 组件使用 useMemo 包裹,只有当 useMemo 依赖项发生变化时,才会重新渲染 ComponentList 组件:
export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); const list = useMemo(() => { return <CountriesList />; }, []); return ( <> <h1>Country settings h1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> {list} ); };
当然,在这个简化的例子中是不行的,因为它没有任何依赖项。那我们该如何简化 Page 页面呢?下面再来看一下它的结构:
export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); return ( <> <h1>Country settings h1> <div css={contentCss}> <CountriesList countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> <SelectedCountry country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> div> ); };
可以看到:
这意味着当 selectedCountry 状态发生变化时,CountriesList 组件不需要重新渲染。savedCountry 状态发生变化时,SelectedCountry 组件也不需要重新渲染。可以使用 useMemo 来包裹它们,以防止不必要的重新渲染:
export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); const list = useMemo(() => { return ( <CountriesList countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> ); }, [savedCountry, countries]); const selected = useMemo(() => { return ( <SelectedCountry country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> ); }, [selectedCountry]); return ( <> <h1>Country settings h1> <div css={contentCss}> {list} {selected} div> ); };
由此总结出第二条规则:「如果组件需要管理状态,就找出渲染树中不依赖于已更改状态的部分,并将其使用 useMemo 包裹,以减少其不必要的重新渲染。」 。
Page 页面已经完美实现了,是时候编写它的子组件了。首先来实现比较复杂的 CountriesList 组件。这个组件一个接收国家列表数据,当在列表中选中一个国家时,会触发 onCountryChanged 回调,并应以不同的颜色突出显示保存的国家:
type CountriesListProps = { countries: Country[]; onCountryChanged: (country: Country) => void; savedCountry: Country; }; export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { const Item = ({ country }: { country: Country }) => { // 根据国家选项是否已选中来切换不同的className const className = savedCountry.id === country.id ? "country-item saved" : "country-item"; const onItemClick = () => onCountryChanged(country); return ( <button className={className} onClick={onItemClick}> <img src={country.flagUrl} /> <span>{country.name} span> button> ); }; return ( <div> {countries.map((country) => ( <Item country={country} key={country.id} /> ))} div> ); };
这里只做了两件事:
根据接收到的 props 来生成 Item 组件,它依赖于onCountryChanged和savedCountry,
遍历 props 中的国家数组,来渲染国家列表.
如果在一个组件渲染期间创建了另一个组件(如上面的 Item 组件),会发生什么呢?从 React 的角度来看,Item 只是一个函数,每次渲染都会返回一个新的结果。它将删除以前生成的组件,包括其DOM树,将其从页面中删除,并生成和装载一个全新的组件。每次父组件重新渲染时,都会使用一个全新的DOM树.
如果简化国家示例来展示这个过程,将是这样的:
const CountriesList = ({ countries }: { countries: Country[] }) => { const Item = ({ country }: { country: Country }) => { useEffect(() => { console.log("Mounted!"); }, []); console.log("Render"); return <div>{country.name} div>; }; return ( <> {countries.map((country) => ( <Item country={country} /> ))} ); };
从性能角度来看,与完全重新创建组件相比,正常的重新渲染的性能会好很多。在正常情况下,带有空依赖项数组的 useEffect 只会在组件完成装载和第一次渲染后触发一次。之后,React 中的轻量级重新渲染过程就开始了,组件不是从头开始创建的,而是只在需要时更新。这里假如有100个国家,当点击按钮时,就会输出100次 Render 和100次 Mounted,Item 组件会重新装载和渲染100次.
解决这个问题最直接的办法就是将 Item 组件移到渲染函数外:
const Item = ({ country }: { country: Country }) => { useEffect(() => { console.log("Mounted!"); }, []); console.log("Render"); return <div>{country.name} div>; }; const CountriesList = ({ countries }: { countries: Country[] }) => { return ( <> {countries.map((country) => ( <Item country={country} /> ))} ); };
这样在点击按钮时,Item组件就会重现渲染100次,只输出100次 Render,而不会重新装载组件。保持了不同组件之间的边界,并使代码更简洁。下面就来看看国家列表组件在修改前后的变化.
修改前:
export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { const Item = ({ country }: { country: Country }) => { // ... }; return ( <div> {countries.map((country) => ( <Item country={country} key={country.id} /> ))} div> ); };
修改后:
type ItemProps = { country: Country; savedCountry: Country; onItemClick: () => void; }; const Item = ({ country, savedCountry, onItemClick }: ItemProps) => { // ... }; export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { return ( <div> {countries.map((country) => ( <Item country={country} key={country.id} savedCountry={savedCountry} onItemClick={() => onCountryChanged(country)} /> ))} div> ); };
现在,每次父组件重新渲染时不会再重新装载 Item 组件,由此可以总结出第三条规则:「不要在一个组件的渲染内创建新的组件。」 。
这个组件比较简单,就是接收一个属性和一个回调函数,并呈现国家信息:
const SelectedCountry = ({ country, onSaveCountry }: { country: Country; onSaveCountry: () => void }) => { return ( <> <ul> <li>Country: {country.name} li> ... // 要渲染的国家信息 ul> <button onClick={onSaveCountry} type="button">Save button> ); };
最后来实现页面的黑暗模式和明亮模式的切换。考虑到当前主题会在很多组件中使用,如果通过 props 传递就会非常麻烦。因此 Context 是比较合适的解决方案.
首先,创建主题 context:
type Mode = 'light' | 'dark'; type Theme = { mode: Mode }; const ThemeContext = React.createContext<Theme>({ mode: 'light' }); const useTheme = () => { return useContext(ThemeContext); };
添加 context provider ,以及切换主题的按钮:
为国家选项根据主题上色:
const Item = ({ country }: { country: Country }) => { const { mode } = useTheme(); const className = `country-item ${mode === "dark" ? "dark" : ""}`; // ... }
这是一种实现页面主题的最常见的方式.
在发现上面组件的问题之前,先来看看导致组件重新渲染的另一个原因:如果一个组件使用 context,当 provider 提供的值发生变化时,该组件就会重新渲染.
再来看看简化的例子,这里记录了渲染结果以避免重新渲染:
const Item = ({ country }: { country: Country }) => { console.log("render"); return <div>{country.name} div>; }; const CountriesList = ({ countries }: { countries: Country[] }) => { return ( <> {countries.map((country) => ( <Item country={country} /> ))} ); }; export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); const list = useMemo(() => <CountriesList countries={countries} />, [ countries ]); return ( <> <h1>Country settings h1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> {list} ); };
每次点击按钮时,页面状态发生变化,页面组件会重新渲染。但 CountriesList 组件使用useMemo缓存了,会独立于该状态,因此不会重新渲染,因此 Item 组件也不会重新渲染.
如果现在添加Theme context,Provider 在 Page 组件中:
export const Page = ({ countries }: { countries: Country[] }) => { // ... const list = useMemo(() => <CountriesList countries={countries} />, [ countries ]); return ( <ThemeContext.Provider value={{ mode }}> // ... ThemeContext.Provider> ); };
context 在 Item 组件中:
const Item = ({ country }: { country: Country }) => { const theme = useTheme(); console.log("render"); return <div>{country.name} div>; };
如果它们只是普通的组件和Hook,那什么都不会发生—— Item 不是 Page 组件的子级,CountriesList 组件因为被缓存而不会重新渲染,所以 Item 组件也不会。但是,本例是提供者-使用者的模式,因此每次提供者提供的值发生变化时,所有使用者都将重新渲染。由于一直在向该值传递新对象,因此每个计数器上都会重新呈现不必要的项。Context 基本绕过了useMemo,使它毫无用处.
解决方法就是确保 provider 中的值不会发生超出需要的变化。这里只需要把它记下来:
export const Page = ({ countries }: { countries: Country[] }) => { // ... const theme = useMemo(() => ({ mode }), [mode]); return ( <ThemeContext.Provider value={theme}> // ... ThemeContext.Provider> ); };
现在计数器就不会再导致所有 Items 重新渲染。下面就将这个解决方案应用于主题组件,以防止不必要的重新渲染:
export const Page = ({ countries }: { countries: Country[] }) => { // ... const [mode, setMode] = useState<Mode>("light"); const theme = useMemo(() => ({ mode }), [mode]); return ( <ThemeContext.Provider value={theme}> <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>Toggle theme button> // ... ThemeContext.Provider> ) }
根据这个结果就可以得出第四条规则:「在使用 context 时,如果 value 属性不是数字、字符串或布尔值,请使用 useMemo 来缓存它。」 。
导致 React 组件重新渲染的时机主要有以下三种
避免不必要的重新渲染的规则如下:
这些规则将有助于从开始就编写高性能的 React 应用程序.
作者:NADIA MAKAREVICH 。
译者:CUGGZ 。
原文:https://www.developerway.com/posts/how-to-write-performant-react-code 。
最后此篇关于如何编写高性能的 React 代码:规则、模式、注意事项的文章就讲到这里了,如果你想了解更多关于如何编写高性能的 React 代码:规则、模式、注意事项的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要在 nginx-ingress 版本上允许来自多个来源的请求:http://localhost:4200、http://localhost:4242 等1.7.1.但我无法对多个来源执行此操作,
我正在部署我使用 APIGILITY 开发的 API到 IIS。由于 IIS 不支持 .htaccess,我试图从 .htaccess 文件的内容创建 web.config 文件。我使用 IISv7.
我正在尝试更改上面 css 样式中的“宽度”规则。在“inspect element”中你可以看到宽度是1008px。我不希望它是 1008px 但它不会让我在 css 样式中更改它你可以看到它被“删
外部css赋值有2种方法,我用的是第一种;大多数网站使用第二种方法。我想知道我是否做错了! 第一种方法: 为几乎每个 css 规则创建一个类并在任何地方使用它们。 blah blah .f_
RDF使用 WEB 标识符 (URIs) 来标识资源,使用属性和属性值来描述资源 RDF 资源、属性和属性值 RDF使用 WEB 标识符来标识事物,并通过属性和属性值来描述资源。 关于资源、属性
我想挖掘特定的 rhs 规则。文档中有一个示例证明这是可能的,但仅适用于特定情况(如下所示)。先来一个数据集来说明我的问题: input {b=100002} 0.2500000 0.250000
我想让 nginx 从网站根目录(:http://localhost:8080/)提供一个静态文件,但它为我的代理通行证提供服务;它提供“/”规则而不是“=/”。 这是我的 nginx 配置的样子:
根据gnu make documentation , 如果一个规则通过一次调用生成多个目标(例如,一个配方执行一个带有多个输出文件的工具),你可以使用 '&:' 规则语法来告诉 make。但是,当在多
我已阅读Firebase Documentation并且不明白什么是 .contains()。 以下是文档中 Firebase 数据库的示例规则: { "rules": { "rooms"
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 6 年前。 Improv
我正在尝试做一些多态性练习,但我无法弄清楚这种多态性是如何工作的。我没有找到任何关于这种练习的深入信息。希望大家能给我一些解释。 练习1: class Top { public void m(
为了调试复杂的 XSLT 转换,我将其分为几个部分:首先构建 %.1.xml,然后使用它构建 %.2.xml ,最后构建 %.3.xml。一切正常,但如果我要求 Make 构建最后一个,Make 总是
我尝试了 hacerrank 的 slove 练习 Click我不知道如何添加这些规则: ► 它可以包含 4 个一组的数字,并用一个连字符“-”分隔。 ► 不得有 4 个或更多连续重复数字。 这是我的
我正在尝试编写一个小测验,我希望“再试一次”按钮遵循与“else”之前的“if”语句相同的规则 using System; public class Program { public stat
在我的 Spring/Boot Java 项目中,我有一组服务方法,例如以下一个: @Override public Decision create(String name, String descr
我正在阅读 Covariant virtual function .上面写着 假设 B::f 覆盖了虚函数 A::f。如果满足以下所有条件,A::f 和 B::f 的返回类型可能不同: 1) The
我工作的公司想要分发(在公共(public)链接中)具有内部签名的应用程序。我很确定 Apple 否认这种事情,但我在官方文档/契约(Contract)中没有找到任何相关信息。 有谁知道它到底是如何工
我是 CSS 新手。我观察到一个奇怪的 CSS 行为,其中一个元素具有以下 CSS 属性 .container .header{ color: #FFFFFF; font-size: 2em;
这个问题在这里已经有了答案: Is there a CSS selector for elements containing certain text? (21 个答案) 关闭 7 年前。
我有以下 CSS: workoutcal.css: .errorlist{ color:red; } 以下基本模板: base.html: {% load static %} {
我是一名优秀的程序员,十分优秀!