第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機立即綁定

在Next.js中實現(xiàn)狀態(tài)設(shè)計模式,讓數(shù)據(jù)獲取更高效,UI管理更清晰

简介

高效的数据获取和UI状态管理是Next.js应用中的关键挑战。如果没有一个结构化的处理方法,冗余的API调用会导致性能下降,混乱的状态管理会使代码复杂度增加。本指南探讨了如何使用状态设计模式来简化数据管理,防止不必要的请求,并改进UI的独立性,从而构建一个更为易维护和高效的應用程序。

状态模式

状态模式是一种行为模式,它使对象能够根据其内部状态动态地改变其行为。通过将特定状态的逻辑封装在单独的类中,此模式促进了更清晰的关注点分离,使代码更易于维护和扩展。

要更好地理解状态模式,可以看看Refactoring Guru的指南,该指南提供了详尽的解释并附有示例。

我们的目标是...

在这篇文章中,我们将探讨状态模式如何帮助简化数据加载、提升性能以及保持用户界面和逻辑的分离。通过将获取状态结构化为独立的对象,我们使代码更清晰、更模块化、更易管理。

让我们跳进去吧!

为什么要在 React/Next.js 中使用状态模式来获取数据?

在 React 和 Next.js 中获取数据可能变得具有挑战性,因为可能会出现多余的 API 调用、不必要的渲染以及复杂的状态管理问题。每次组件重新渲染可能会触发新的请求,导致性能下降和资源浪费。此外,处理不同的状态 — 加载、成功和失败 — 可能会导致代码臃肿且难以维护。

状态模式如何帮助我们解决问题

状态模式通过将每种状态(加载、成功、失败)封装到单独的对象或类中,提供了一种结构化的处理方法。这样可以确保。

  • 关注分离(分离关注点) — 将UI逻辑与数据获取逻辑分离,使组件更加清晰简洁。
  • 可重用性和可维护性 — 状态转换由集中管理,提高了代码的可扩展性。
  • 改善性能 — 通过控制数据获取的时机和方式,避免了重复的API调用。
  • 灵活性 — 可以轻松扩展以支持新的状态或行为,而无需修改现有的逻辑。

通过采用这种模式,开发人员可以开发更高效、更易于预测和更可扩展的 React 或 Next.js 应用程序。

理解状态机设计模式是什么

有限状态自动机

什么是状态模式?

状态模式让对象可以随内部状态的不同而改变行为,比如红绿灯:

  • 当红灯亮起时,汽车必须停下。
  • 当黄灯亮起时,汽车准备停下。
  • 当绿灯亮起时,汽车可以继续前行。

每个状态(红色、黄色、绿色)都要求不同的行为,系统在这几个状态之间平滑过渡。

状态模式的工作原理

在 UI 开发中,状态模式有助于动态地管理不同状态。例如,在 React/Next.js 应用中获取数据的过程中,我们通常会处理这样的状态。

已定义的状态:

  • 闲置 — 默认状态,当没有正在进行的数据获取任务时(等待开始获取数据)。
  • 正在加载数据 — 在获取数据的过程中。
  • 成功接收 — 当数据成功接收时。
  • 出错 — 如果在获取数据过程中发生错误。
  • 已取消获取 — 如果数据获取任务被用户手动中止或取消。

从一个状态到另一个状态的转变

  • 空闲状态开始,当没有进行获取操作时。
  • 当发起一个获取操作时,进入加载状态。
  • 如果获取成功,则变为成功状态。
  • 如果获取失败,则变为错误状态。
  • 如果获取被手动取消,则变为取消状态。

状态变化

UI开发中的状态模式(State Pattern)

使用状态模式可以让UI状态管理更加有条理和容易理解:

  • 封装状态逻辑 — 每个状态都有其特定的行为,这使得修改变得容易。
  • 提升可读性和可维护性 — 避免在组件中分散复杂的 if-else 条件。
  • 增强可重用性 — 状态可以在不同的组件和页面中重复利用。

应用这种模式后,UI 组件变得更加 更灵活、更可预测和更易管理,从而带来更流畅、更佳的用户体验。

定目标

在以下内容中,我们明确实现的目标。当前我们有一个客户端组件在我们的 Next.js 应用程序中,该组件在每次渲染时触发获取 Lighthouse 报告。然而,在页面间导航时,加载进度会被重置,导致不必要的重复获取并影响用户体验。

为解决此问题,我们计划实现状态设计模式(State Design Pattern),以在路由变化时保持加载状态的一致性。这将帮助我们更高效地管理诸如“加载中”“成功”、和“错误”等状态,从而提供更流畅和一致的用户体验。

每次加载时,客户端的组件都会重新获取数据。

如何在 Next.js 中实现状态模式(State Pattern)
定义状态接口和具体状态类

我们将专注于以下状态:“idle”“loading”“success”。这些状态代表在 Next.js 应用程序中的数据获取流程的不同阶段,通过明确定义每个状态的接口,我们确保了对 UI 过渡和 API 响应的管理更加有条不紊。这样我们就能以结构化的方法来管理 UI 过渡和 API 响应。

需要落实的州

定义状态界面

我们可以从为每个状态定义一个 TypeScript 的接口开始,确保类型安全并保持可维护性。

    export interface LighthouseReportState {  
        /**  

* 触发报告的加载并启动状态变化。  
         */  
        load: () => void;  
        /**  

* 返回报告的Promise对象。如果报告还未加载,则返回null。  
         */  
        getReport: () => Promise<LighthouseReport | null>;  
        readonly status: Status;  
    }  

    export type Status = 'loading' | 'success' | 'idle';  

    export interface LighthouseReport {  
      // 数据模型详情请参见相关文档...  
    }

状态界面:此接口将作为UI组件和上下文之间的API,确保UI与状态系统之间的交互是标准化的。

这样可以确保用户界面与数据流逻辑保持独立性,同时又能平滑地与系统的当前状态互动。

定义状态类定义

每个状态类(State 类)代表获取报告过程中的不同阶段,并称为LighthouseReportState接口。

    class IdleState implements LighthouseReportState {  

        get status(): Status {  
            return 'idle';  
        }  

        constructor(private readonly context: StateContext) {}  

        load() {  
            this.context.state = new LoadingState(this.context);  
        }  

        async getReport(): Promise<null> {  
            return null;  
        }  
    }  

    class LoadingState implements LighthouseReportState {  
        private readonly report: Promise<LighthouseReport | null>;  

        get status(): Status {  
            return 'loading';  
        }  

        constructor(private readonly context: StateContext) {  
            this.context.notifyProgress(0);  

            this.report = fetchLighthouseReport().then(report => {  
                this.context.state = new SuccessState(this.context, report);  
                return report;  
            }).catch(error => {  
                this.context.state = new IdleState(this.context);  
                console.warn('但是报告获取失败的话,就回到空闲状态,并且在控制台中警告失败原因。', error);  
                return null;  
            });  
        }  

        load() {  
            // 因为报告正在加载,所以这里不需要做任何操作。  
        }  

        async getReport(): Promise<LighthouseReport | null> {  
            return this.report;  
        }  
    }  

    class SuccessState implements LighthouseReportState {  

        get status(): Status {  
            return 'success';  
        }  

        constructor(  
                private readonly context: StateContext,   
                private readonly report: LighthouseReport  
        ) {}  

        load() {  
            this.context.state = new LoadingState(this.context);  
        }  

        async getReport(): Promise<LighthouseReport> {  
            return this.report;  
        }  
    }
空闲状态
  • 这是尚未请求任何报告时的默认状态。
  • status 设为 'idle'
  • 调用 load() 会使状态变为 LoadingState
加载中(正在获取报告)
  • 这个状态开始了抓取过程。
  • 将状态设置为 'loading'
  • 报告抓取是异步的,如果失败,状态会变成 IdleState
  • 调用 load() 没有效果,因为它已经在抓取了。

加载成功(报告已加载完毕)

  • 报告成功获取并已储存。
  • status 设定为 'success'
  • 调用 getReport() 返回报告。
  • 调用 load() 将状态重置为加载状态,允许发起新的加载请求。
这些州是怎么合作的
  • StateContext 对象负责管理状态转换。
  • 当请求获取报告时,IdleState 变为 LoadingState
  • 如果获取成功,LoadingState 变为 SuccessState
  • 如果获取失败,会退回到 IdleState(错误情况则由后续的 ErrorState 处理)。
状态管理:处理状态变化

StateContext 类作为核心状态管理器,确保任何时候都有适当的状态被激活。它支持在这些状态之间(IdleStateLoadingStateSuccessState)流畅切换,将状态管理与 UI 和业务逻辑分离。

StateContext的主要职责:

  1. 保持当前状态 — 它保持了灯塔报告获取过程当前状态的引用。
  2. 允许状态转换 — 提供了 set state() 方法,用于在不同状态间动态切换。
  3. 封装初始状态 — 默认初始状态为 IdleState
    // 定义一个状态上下文
    export class StateContext {  
        // 设置状态
        set state(state: LighthouseReportState) {  
            this._state = state;  
        }  
        // 获取状态
        get state() {  
            return this._state;  
        }  
        // 私有状态初始化为IdleState
        private _state: LighthouseReportState = new IdleState(this);  
    }

构造函数将 _state 初始化为 IdleState,确保数据获取过程从空闲状态开始。这种设计确保每个状态都能独立运作,同时通过 StateContext 进行集中管理,使系统更易于维护和扩展性更强。

在 React 中使用 Context: 集成状态管理

为了在 Next.js 项目中无缝地使用 状态模式(State Pattern),我们将利用 React 的 Context API, 集成 StateContext

1. 创建 React 上下文用于状态管理

LighthouseReportReactContext 提供了获取 Lighthouse 报告的过程的全局状态管理。

    'use client';  

    import React from "react";  
    import { StateContext } from "@/app/state";  

    const stateContext = new StateContext();  
    export const LighthouseReportReactContext = React.createContext(stateContext);
2. 在组件中利用状态环境

现在,让我们创建一个使用 StateContext 来抓取 Lighthouse 报告内容的组件。

    const LighthouseDashboard = () => {  
        const context = useContext(LighthouseReportReactContext);  

        const [report, setReport] = useState<LighthouseReport | null>(null);  
        const [loading, setLoading] = useState<boolean>(context.state.status === 'loading');  

        const updateReports = () => {  
            console.log('[LighthouseReportState] 设置报告状态:', context.state.status);  

            new Promise<LighthouseReport | null>(async (resolve) => {  
                resolve(context.state.getReport());  
            }).then(report => {  
                setReport(report);  
            }).catch(error => {  
                console.warn('[LighthouseReportState] 获取 Lighthouse 报告失败:', error);  
            });  
        };  

        useEffect(() => {  
            updateReports();  
        }, []);  

        return (  
            <div>  
                <div>  
                    <StatusDisplay status={context.state.status}/>  
                </div>  

                <div>  
                    <button  
                        onClick={() => context.state.load()}  
                        disabled={context.state.status === 'loading'}  
                    >  
                        <RefreshCcw className={`w-5 h-5 ${context.state.status === 'loading' ? 'animate-pulse' : ''}`}/> 重新加载  
                    </button>  
                </div>  

                <div>  
                    {loading && Array(4).fill(0).map((_, i) => (  
                        <Skeleton key={i} className="h-24 w-full"/>  
                    ))}  

                    {!loading && report && Object.entries(report).map(([key, score]) => (  
                        <Card key={key} className="shadow-lg rounded-2xl p-4">  
                            <CardHeader className="flex justify-between items-center">  
                                <CardTitle className="">{key.charAt(0).toUpperCase() + key.slice(1)}</CardTitle>  
                                {getScoreIcon(score)}  
                            </CardHeader>  
                            <CardContent className="">  
                                <div className="flex justify-between text-sm font-medium">  
                                    <span className={getScoreColor(score)}>{score}%</span>  
                                    <span>100%</span>  
                                </div>  
                                <Progress value={score} className="mt-2"/>  
                            </CardContent>  
                        </Card>  
                    ))}  
                </div>  
            </div>  
        );  

    }

点击按钮会调用 StateContext 中当前状态的 load() 方法,触发状态的转换。由于 StateContext 动态地管理状态,这确保了组件在正确启动数据获取流程的同时不会,在已经处于加载状态时重复发起请求。

状态从空闲变为加载,然后变为成功,但UI没有更新。这是因为React无法自动检测到context.state的变化。因此,React只会根据状态或属性的变化来重新渲染组件。我们直接修改了context.state,React没有意识到这些更新,因此不会重新渲染。

React 不检测状态变化。

3. 在 StateContext 中实现观察者模式来触发重新渲染内容

为了解决UI不响应状态变化的问题,我们将StateContext更新,使其实现一个简单的观察者模式。这使得组件可以订阅状态更新,并在状态更新时自动触发重新渲染。

实施

  1. StateContext 中添加一个 subscribe 方法,用于注册状态变化监听器。
  2. 当设置 state 时,每当状态更新时,通知订阅者。
    export class StateContext {  
        private _state: LighthouseReportState = new IdleState(this);  
        private _stateSubject?: (state: LighthouseReportState) => void;  

        // 设置新的状态并通知订阅者  
        set state(state: LighthouseReportState) {  
            this._state = state;  
            this._stateSubject?.(state); // 通知订阅者  
        }  

        // 获取当前状态  
        get state() {  
            return this._state;  
        }  

        // 订阅状态  
        subscribe(callback: (state: LighthouseReportState) => void) {  
            this._stateSubject = callback; // 存储回调以在状态变化时通知订阅者  
            return () => {  
                this._stateSubject = undefined; // 取消订阅  
            };  
        }  
    }
4. 更新组件以响应状态的改变

现在我们在 StateContext 实现了观察者模式,下一步就是调整这个组件,让它能正确响应状态变化,并在状态更新时触发重渲染。

    const LighthouseDashboard = () => {  
        const context = useContext(LighthouseReportReactContext);  

        const [report, setReport] = useState<LighthouseReport | null>(null);  
        const [loading, setloading] = useState<boolean>(context.state.status === 'loading');  

        const onStateChange = () => {  
            setloading(context.state.status === 'loading');  
            updateReports();  
        }  

        useEffect(() => {  
            const unsubscribe = context.subscribe(onStateChange);  
            return () => {  
                unsubscribe();  
            };  
        }, [context]);  

        // ... 其他代码      
    }

这里发生了什么事?

  • **onStateChange**:当状态改变时调用此函数。它更新 loading 状态,并触发 updateReports 函数以加载数据。
  • **useEffect**:组件使用 context.subscribe(onStateChange) 订阅状态变化。当组件卸载时,它会从上下文中断开订阅(清理)。
  • **updateReports**:此函数根据当前状态加载报告,然后将其存储在 report 状态变量中。

政府在行动中

其他组件中重用状态值

使用 React Context 和 State 设计模式的一个关键好处是,可以轻松地在多个组件间重用相同的状态。这确保了应用中数据的同步,消除了重复的数据获取和状态管理的需要。

在本节中,我们将展示如何复用 StateContext 组件来在组件间共享同步数据,例如在概览页面和其他页面之间。

概览页示例

比如说,我们有一个仪表板页面,页面中包含一个显示性能数据的LighthouseCardPage组件充当主布局,包含一个网格,网格内有各种卡片,其中就包括LighthouseCard

    export default async function 页面组件() {  

        return (  
            <main>  
                <h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>  
                    一个仪表盘  
                </h1>  
                <div className="grid grid-cols-2 grid-rows-5 gap-4">  
                    <LighthouseCard />  
                    <E2ETestsCard />  
                </div>  
            </main>  
        );  
    }  

LighthouseCard 使用 LighthouseReportReactContext 异步获取 Lighthouse 报告数据。它将报告更新到组件状态中,并将数据映射到一个数组 (reportData),其中包括性能、可访问性、最佳实践和 SEO 的分数,每个分数都配有相应的图标。

这种方法确保了各组件间的数据同步,避免了重复获取数据,并利用共享的StateContext

    const LighthouseCard = () => {  
        const context = useContext(LighthouseReportReactContext);  
        const [report, setReport] = useState<LighthouseReport | null>(null);  

        useEffect(() => {  
            new Promise<LighthouseReport | null>(async (resolve) => {  
                resolve(await context.state.getReport());  
            }).then(report => {  
                setReport(report);  
            }).catch(error => {  
                console.warn('获取 Lighthouse 报告失败:', error);  
            });  
        }, []);  

        const reportData = [  
            { key: 'performance', report: report?.performance, label: '性能表现', icon: Gauge },  
            { key: 'accessibility', report: report?.accessibility, label: '可访问性', icon: Accessibility },  
            { key: 'bestPractices', report: report?.bestPractices, label: '最佳实践', icon: ShieldCheck },  
            { key: 'seo', report: report?.seo, label: 'SEO', icon: Search },  
        ];  

      return(  
        // ... 使用状态数据的用户界面  
      )  

    }

该界面具有一个采用网格布局的仪表板,显示关键指标,如性能、可访问性、最佳做法和SEO得分。数据异步从共享状态获取,确保所有组件保持同步和最新。

重用

试一试:在应用中实现错误和取消操作的状态吧。

作为现有实现的扩展,尝试加入 错误取消 状态,以更好地应对异常情况。目标是确保在数据获取出错或请求被取消时,应用能有恰当的反应。

  • 实现一个错误状态,用于捕获并显示数据获取过程中可能出现的任何问题,例如网络错误或响应错误。
  • 添加一个取消状态,以处理数据获取被用户中断或取消的情况,确保良好的用户感受。
  • 更新用户界面以反映这些新状态,在请求失败或被取消时显示适当的提示或备用内容。

这个挑战将帮助你提高应用程序的稳定性,并确保它能够更好地应对各种实际应用场景。

试着实现一下这些 States。

结尾

在这篇文章中,我们探讨了如何使用状态模式React Context来管理和同步数据获取的状态。我们了解了如何创建一个可扩展且可重用的状态管理解决方案,将UI与数据流分开,从而使代码更清晰、更易于维护。此外,我们将状态扩展以处理各种状态,比如idleloadingsuccess

一个关键的收获是,可以将状态抽象化,使其能够用于其他数据获取场景。通过在获取函数中实现依赖注入,相同的状态管理逻辑可以应用于不同的数据来源,从而使它更加灵活和适应性强。

感谢您的阅读!谢谢阅读!希望这份指南能帮助您更好地了解如何在React中运用设计模式,来创建更健壮和高效的React应用。如果您有任何想法或疑问,随时分享您的想法——我很想听听您的想法,并不断完善我的内容!

點擊查看更多內(nèi)容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優(yōu)質(zhì)文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續(xù)努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優(yōu)惠券免費領(lǐng)

立即參與 放棄機會
微信客服

購課補貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學習伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號

舉報

0/150
提交
取消