MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 新手到高手的条件渲染进阶之路

2024-03-227.6k 阅读

条件渲染基础概念

在 React 中,条件渲染是一种根据特定条件来决定是否渲染某些组件或者元素的技术。它是构建动态用户界面的关键技巧之一。与传统的 HTML 不同,React 允许我们在 JavaScript 代码中使用逻辑判断来控制 UI 的呈现。

在 React 中,我们可以使用 JavaScript 原生的条件语句,比如 if - elseswitch - case 等,也可以使用三元运算符 ? : 来实现条件渲染。

使用 if - else 进行条件渲染

最直观的方式就是使用 JavaScript 的 if - else 语句。假设我们有一个简单的组件,根据用户是否登录来显示不同的内容。

import React from'react';

function UserGreeting() {
    return <p>欢迎回来!</p>;
}

function GuestGreeting() {
    return <p>请登录。</p>;
}

function Greeting({ isLoggedIn }) {
    if (isLoggedIn) {
        return <UserGreeting />;
    } else {
        return <GuestGreeting />;
    }
}

export default function App() {
    const isLoggedIn = true;
    return (
        <div>
            <Greeting isLoggedIn={isLoggedIn} />
        </div>
    );
}

在上述代码中,Greeting 组件接收一个 isLoggedIn 的属性。根据这个属性的值,Greeting 组件决定渲染 UserGreeting 还是 GuestGreeting 组件。

使用三元运算符进行条件渲染

三元运算符是一种简洁的条件判断方式,在 React 中也经常用于条件渲染。

import React from'react';

function UserGreeting() {
    return <p>欢迎回来!</p>;
}

function GuestGreeting() {
    return <p>请登录。</p>;
}

function Greeting({ isLoggedIn }) {
    return isLoggedIn? <UserGreeting /> : <GuestGreeting />;
}

export default function App() {
    const isLoggedIn = false;
    return (
        <div>
            <Greeting isLoggedIn={isLoggedIn} />
        </div>
    );
}

这里通过三元运算符 isLoggedIn? <UserGreeting /> : <GuestGreeting />,根据 isLoggedIn 的真假来决定渲染哪个组件。这种方式比 if - else 语句更简洁,适合简单的条件判断。

条件渲染中的逻辑与运算

在 React 条件渲染中,逻辑与运算符 && 是一个非常有用的工具。它可以让我们在某个条件为真时才渲染一个组件。

import React from'react';

function LoadingIndicator() {
    return <p>加载中...</p>;
}

function DataDisplay({ isLoading, data }) {
    return (
        <div>
            {isLoading && <LoadingIndicator />}
            {!isLoading && <p>{data}</p>}
        </div>
    );
}

export default function App() {
    const isLoading = true;
    const data = '这是加载的数据';
    return (
        <div>
            <DataDisplay isLoading={isLoading} data={data} />
        </div>
    );
}

DataDisplay 组件中,当 isLoadingtrue 时,isLoading && <LoadingIndicator /> 会渲染 LoadingIndicator 组件。而当 isLoadingfalse 时,!isLoading && <p>{data}</p> 会渲染包含数据的 <p> 标签。

逻辑与运算的原理是:如果 && 左边的表达式为真(在 JavaScript 中,非 null,非 undefined,非 false,非 0,非空字符串都为真),则返回右边的表达式;如果左边的表达式为假,则返回左边的表达式。在 React 中,假值(如 falsenullundefined0)不会在 UI 中渲染任何内容,所以当条件为假时,&& 右边的组件不会被渲染。

避免条件渲染中的陷阱

在进行条件渲染时,有一些常见的陷阱需要注意。

空值渲染问题

有时候我们可能会不小心渲染出一些空值。比如,我们期望在某个条件下渲染一个组件,但是由于数据未准备好或者条件判断错误,可能会渲染出 null 或者 undefined

import React from'react';

function DataComponent({ data }) {
    // 假设 data 可能为 null
    return <p>{data.message}</p>;
}

export default function App() {
    const data = null;
    return (
        <div>
            <DataComponent data={data} />
        </div>
    );
}

在上述代码中,如果 datanull,访问 data.message 会导致 TypeError。为了避免这种情况,我们需要在渲染之前进行数据的有效性检查。

import React from'react';

function DataComponent({ data }) {
    if (!data) {
        return null;
    }
    return <p>{data.message}</p>;
}

export default function App() {
    const data = null;
    return (
        <div>
            <DataComponent data={data} />
        </div>
    );
}

通过在 DataComponent 中检查 data 是否为有效数据,当 datanull 或者 undefined 时,直接返回 null,避免了运行时错误。

复杂条件嵌套

随着应用的增长,条件渲染的逻辑可能会变得非常复杂,出现多层嵌套的 if - else 语句。这会使代码难以阅读和维护。

import React from'react';

function ComplexComponent({ userType, isLoggedIn, hasPermission }) {
    if (isLoggedIn) {
        if (userType === 'admin') {
            if (hasPermission) {
                return <p>管理员,有操作权限</p>;
            } else {
                return <p>管理员,无操作权限</p>;
            }
        } else if (userType === 'user') {
            return <p>普通用户</p>;
        }
    } else {
        return <p>请登录</p>;
    }
}

export default function App() {
    const userType = 'admin';
    const isLoggedIn = true;
    const hasPermission = true;
    return (
        <div>
            <ComplexComponent userType={userType} isLoggedIn={isLoggedIn} hasPermission={hasPermission} />
        </div>
    );
}

为了避免这种复杂的嵌套,可以将逻辑进行拆分,使用辅助函数或者对象映射来简化条件判断。

import React from'react';

function getMessage(userType, isLoggedIn, hasPermission) {
    if (!isLoggedIn) {
        return '请登录';
    }
    if (userType === 'admin') {
        return hasPermission? '管理员,有操作权限' : '管理员,无操作权限';
    }
    if (userType === 'user') {
        return '普通用户';
    }
    return '';
}

function ComplexComponent({ userType, isLoggedIn, hasPermission }) {
    const message = getMessage(userType, isLoggedIn, hasPermission);
    return <p>{message}</p>;
}

export default function App() {
    const userType = 'admin';
    const isLoggedIn = true;
    const hasPermission = true;
    return (
        <div>
            <ComplexComponent userType={userType} isLoggedIn={isLoggedIn} hasPermission={hasPermission} />
        </div>
    );
}

通过将条件判断逻辑提取到 getMessage 函数中,ComplexComponent 的渲染逻辑变得更加清晰简洁。

高阶条件渲染技巧

条件渲染与状态管理

在 React 应用中,条件渲染通常与状态管理紧密结合。状态的变化会触发条件的改变,从而导致 UI 的更新。

import React, { useState } from'react';

function ToggleButton() {
    const [isOn, setIsOn] = useState(false);

    const handleClick = () => {
        setIsOn(!isOn);
    };

    return (
        <div>
            <button onClick={handleClick}>{isOn? '关闭' : '打开'}</button>
            {isOn && <p>按钮已打开</p>}
        </div>
    );
}

export default function App() {
    return (
        <div>
            <ToggleButton />
        </div>
    );
}

ToggleButton 组件中,通过 useState 钩子来管理 isOn 状态。当按钮被点击时,isOn 的状态发生变化,从而影响条件渲染。如果 isOntrue,则渲染 <p>按钮已打开</p>

条件渲染与组件复用

有时候,我们可能需要在不同的条件下复用某些组件,但传递不同的属性。

import React from'react';

function Button({ text, onClick }) {
    return <button onClick={onClick}>{text}</button>;
}

function LoginForm() {
    const handleSubmit = () => {
        console.log('登录提交');
    };
    return (
        <form>
            <input type="text" placeholder="用户名" />
            <input type="password" placeholder="密码" />
            <Button text="登录" onClick={handleSubmit} />
        </form>
    );
}

function RegisterForm() {
    const handleSubmit = () => {
        console.log('注册提交');
    };
    return (
        <form>
            <input type="text" placeholder="用户名" />
            <input type="password" placeholder="密码" />
            <Button text="注册" onClick={handleSubmit} />
        </form>
    );
}

function App({ isRegister }) {
    return isRegister? <RegisterForm /> : <LoginForm />;
}

export default function MainApp() {
    const isRegister = false;
    return (
        <div>
            <App isRegister={isRegister} />
        </div>
    );
}

在上述代码中,Button 组件被 LoginFormRegisterForm 复用,但传递了不同的 textonClick 属性。App 组件根据 isRegister 的条件来决定渲染 LoginForm 还是 RegisterForm

服务器端渲染中的条件渲染

在 React 应用进行服务器端渲染(SSR)时,条件渲染也有一些特殊的考虑。

同构渲染中的条件差异

在同构渲染(即服务器端渲染和客户端渲染使用相同的代码)中,有些条件判断在服务器和客户端可能会有不同的行为。比如,访问浏览器特有的 API(如 windowdocument)在服务器端是不存在的。

import React from'react';

function BrowserSpecificComponent() {
    if (typeof window === 'undefined') {
        return null;
    }
    return <p>这是浏览器特定的组件</p>;
}

export default function App() {
    return (
        <div>
            <BrowserSpecificComponent />
        </div>
    );
}

在上述代码中,BrowserSpecificComponent 组件在服务器端渲染时,由于 typeof window === 'undefined',会返回 null,避免了在服务器端访问不存在的 window 对象导致的错误。而在客户端渲染时,会正常渲染出 <p>这是浏览器特定的组件</p>

服务器端数据获取与条件渲染

在服务器端渲染时,通常需要在渲染前获取数据。根据获取的数据进行条件渲染。

import React from'react';
import { renderToString } from'react - dom/server';

async function getData() {
    // 模拟异步数据获取
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ message: '服务器端获取的数据' });
        }, 1000);
    });
}

function DataComponent({ data }) {
    if (!data) {
        return <p>数据加载中...</p>;
    }
    return <p>{data.message}</p>;
}

async function serverRender() {
    const data = await getData();
    const html = renderToString(<DataComponent data={data} />);
    return html;
}

serverRender().then((html) => {
    console.log(html);
});

在上述代码中,serverRender 函数在服务器端获取数据,然后根据数据的状态进行条件渲染。如果数据未获取到,渲染 <p>数据加载中...</p>,获取到数据后,渲染包含数据的 <p> 标签。

条件渲染的性能优化

减少不必要的渲染

在 React 中,每次状态或者属性变化都会触发组件的重新渲染。如果条件渲染的逻辑不合理,可能会导致不必要的渲染,影响性能。

import React, { useState } from'react';

function ChildComponent({ value }) {
    console.log('ChildComponent 渲染');
    return <p>{value}</p>;
}

function ParentComponent() {
    const [count, setCount] = useState(0);
    const [isVisible, setIsVisible] = useState(true);

    const handleClick = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <button onClick={handleClick}>点击计数: {count}</button>
            {isVisible && <ChildComponent value={count} />}
            <button onClick={() => setIsVisible(!isVisible)}>切换可见性</button>
        </div>
    );
}

export default function App() {
    return (
        <div>
            <ParentComponent />
        </div>
    );
}

在上述代码中,每次点击 点击计数 按钮,ParentComponent 会重新渲染,由于 ChildComponent 依赖于 ParentComponentcount 属性,所以 ChildComponent 也会重新渲染。即使 isVisiblefalseChildComponent 不显示,它依然会因为 ParentComponent 的重新渲染而进行不必要的渲染。

为了避免这种情况,可以使用 React.memo 来包裹 ChildComponent

import React, { useState } from'react';

function ChildComponent({ value }) {
    console.log('ChildComponent 渲染');
    return <p>{value}</p>;
}

const MemoizedChildComponent = React.memo(ChildComponent);

function ParentComponent() {
    const [count, setCount] = useState(0);
    const [isVisible, setIsVisible] = useState(true);

    const handleClick = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <button onClick={handleClick}>点击计数: {count}</button>
            {isVisible && <MemoizedChildComponent value={count} />}
            <button onClick={() => setIsVisible(!isVisible)}>切换可见性</button>
        </div>
    );
}

export default function App() {
    return (
        <div>
            <ParentComponent />
        </div>
    );
}

通过 React.memo 包裹 ChildComponent,只有当 ChildComponentprops 发生变化时,它才会重新渲染。这样,当点击 点击计数 按钮,isVisiblefalse 时,MemoizedChildComponent 不会进行不必要的渲染。

使用 shouldComponentUpdate

在类组件中,可以使用 shouldComponentUpdate 方法来控制组件是否需要重新渲染。

import React, { Component } from'react';

class ChildComponent extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.value!== nextProps.value;
    }

    render() {
        console.log('ChildComponent 渲染');
        return <p>{this.props.value}</p>;
    }
}

class ParentComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
            isVisible: true
        };
    }

    handleClick = () => {
        this.setState((prevState) => ({
            count: prevState.count + 1
        }));
    };

    toggleVisibility = () => {
        this.setState((prevState) => ({
            isVisible:!prevState.isVisible
        }));
    };

    render() {
        return (
            <div>
                <button onClick={this.handleClick}>点击计数: {this.state.count}</button>
                {this.state.isVisible && <ChildComponent value={this.state.count} />}
                <button onClick={this.toggleVisibility}>切换可见性</button>
            </div>
        );
    }
}

export default function App() {
    return (
        <div>
            <ParentComponent />
        </div>
    );
}

ChildComponent 类组件中,shouldComponentUpdate 方法比较当前 propsvalue 和下一个 propsvalue。只有当 value 发生变化时,才会返回 true,允许组件重新渲染。这样可以避免不必要的渲染,提高性能。

条件渲染在实际项目中的应用场景

用户权限控制

在企业级应用中,根据用户的权限来显示不同的功能菜单是常见的需求。

import React from'react';

function AdminMenu() {
    return (
        <ul>
            <li>用户管理</li>
            <li>权限管理</li>
        </ul>
    );
}

function UserMenu() {
    return (
        <ul>
            <li>个人资料</li>
            <li>订单查询</li>
        </ul>
    );
}

function App({ userRole }) {
    return userRole === 'admin'? <AdminMenu /> : <UserMenu />;
}

export default function MainApp() {
    const userRole = 'user';
    return (
        <div>
            <App userRole={userRole} />
        </div>
    );
}

在上述代码中,App 组件根据 userRole 的值来决定渲染 AdminMenu 还是 UserMenu,实现了基于用户权限的菜单显示控制。

多语言支持

对于国际化的应用,需要根据用户设置的语言来显示不同的文本。

import React from'react';

const en = {
    greeting: 'Hello',
    goodbye: 'Goodbye'
};

const zh = {
    greeting: '你好',
    goodbye: '再见'
};

function LanguageSelector({ language }) {
    const messages = language === 'en'? en : zh;
    return (
        <div>
            <p>{messages.greeting}</p>
            <p>{messages.goodbye}</p>
        </div>
    );
}

export default function App() {
    const language = 'zh';
    return (
        <div>
            <LanguageSelector language={language} />
        </div>
    );
}

这里通过 language 的值来选择不同语言的文本对象,从而实现多语言的条件渲染。

条件渲染与 React 生态系统

与 React Router 的结合

React Router 是 React 应用中常用的路由库。在路由中,经常需要根据路由的状态进行条件渲染。

import React from'react';
import { BrowserRouter as Router, Routes, Route } from'react - router - dom';

function Home() {
    return <p>这是首页</p>;
}

function About() {
    return <p>这是关于页面</p>;
}

function App() {
    return (
        <Router>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>
        </Router>
    );
}

export default App;

在上述代码中,Routes 组件根据当前的 URL 路径来决定渲染哪个组件。这本质上也是一种条件渲染,根据路由条件来动态显示不同的页面组件。

与 Redux 的结合

Redux 是一个用于管理 React 应用状态的库。在 Redux 应用中,条件渲染通常依赖于 Redux 中的状态。

import React from'react';
import { useSelector } from'react - redux';

function CartIndicator() {
    const cartItems = useSelector((state) => state.cart.items);
    return (
        <div>
            {cartItems.length > 0 && <p>购物车中有 {cartItems.length} 件商品</p>}
        </div>
    );
}

export default CartIndicator;

CartIndicator 组件中,通过 useSelector 从 Redux 状态中获取购物车中的商品列表 cartItems。然后根据 cartItems 的长度进行条件渲染,如果购物车中有商品,则显示购物车商品数量的提示。

通过以上对 React 条件渲染的深入探讨,从基础概念到高阶技巧,从性能优化到实际应用场景以及与 React 生态系统的结合,希望能帮助你从 React 条件渲染的新手逐步成长为高手,更加熟练地构建出高效、动态的 React 应用程序。