46、React开发全析:从生命周期到钩子函数

React开发全析:从生命周期到钩子函数

1. React组件生命周期钩子

React组件和Angular、Vue.js组件一样,有特定的生命周期。对于类组件而言,要接入这个生命周期,只需在组件中实现特定的方法。

以下是一个示例:

import React from 'react';
import ReactDOM from 'react-dom';

export class LifecycleAwareComponent extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        // 渲染逻辑
    }
    componentDidMount() {
        console.log('componentDidMount lifecycle hook called');
    }
}

在这个例子中,使用了 componentDidMount 生命周期钩子,它会在组件初始化完成后立即被调用。

React还有许多其他的生命周期钩子,Wojciech Maj制作了一个很棒的交互式图表来展示这些钩子,可访问: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram

下面是常用生命周期钩子的简单说明:
| 生命周期钩子函数 | 描述 |
| — | — |
| componentDidMount | 组件实例化完成,props和state初始化并渲染(通常在DOM中)后调用。可在此发起网络请求加载数据、连接存储(如Redux)等。 |
| componentDidUpdate | 组件更新完成(如props或state改变)后调用。可根据组件新值实现副作用,如发起新的网络请求获取数据。该钩子接收前一个props和state作为参数,便于识别具体变化。 |
| componentWillUnmount | 组件即将销毁时调用。可用于清理操作,如移除存储订阅、取消正在进行的网络请求等。 |

若想尝试更全面的示例,可按以下步骤操作:
1. 安装所需依赖:使用 yarn install (或 npm install )。
2. 运行应用:使用 yarn start (或 npm start )。
3. 启动后,点击不同按钮时查看控制台,观察每个钩子的调用情况。

2. 事件的发射与处理

React组件间的通信不限于父组件向子组件传递props,子组件也能发射事件供祖先组件处理。

之前的例子中,我们已了解如何定义回调函数来响应事件。以 Calculator 组件为例:

render() {
    return (
        // ...
        <button onClick={() => this.add(1)}>+1</button>
        // ...
    );
}
add(value) {
    this.setState((state, props) => {
        return {currentResult: state.currentResult + value}
    });
}

在这个例子中,组件将按钮子组件的点击事件关联到本地函数。我们已知道父组件如何监听和响应子组件发射的事件,下面看看子组件如何发射自定义事件。

解决方案很简单:父组件可将回调函数作为props传递给子组件,在React中,这被称为渲染props。它们的工作方式与其他props类似,但期望接收一个函数并返回一个React元素,用于组件组合。

以下是父组件和子组件的示例:

// 父组件
import React from 'react';
import {ChildComponent} from "./ChildComponent";

export class ParentComponent extends React.Component {
    render() {
        return (
            <ChildComponent onAdd={this.add}/>
        );
    }
    add(value) { 
        console.log(`Parent has received the following value: ${value}`);
    }
}

// 子组件
import React from 'react';

export class ChildComponent extends React.Component {
    render() {
        return (
            <button onClick={() => this.props.onAdd(1)}>+1</button>
        );
    }
}

在这个简单的例子中, ParentComponent 定义了 add 方法,并通过 onAdd prop传递给 ChildComponent ChildComponent 接收该方法,并在按钮点击时调用。

使用这类回调函数时, this 关键字的使用可能会有陷阱,可参考: https://reactjs.org/docs/handling-events.html 。实际上,使用TypeScript可避免此类问题。

若要修复问题,需将:

add(value) {
    console.log(`Parent has received the following value: ${value}`);
}

改为:

add = (value) => {
    console.log(`Parent has received the following value: ${value}`);
}

这样修改后,调用 this.add 就能按预期工作。否则,在 ChildComponent 的点击事件处理程序中调用时, this 会指向传递给 ChildComponent 的props对象。

渲染props是简单的props,但有特定用途。它们有助于在组件间复用逻辑,还能实现组件间的简单通信。

3. 远距离组件间的通信

前面展示了组件向子组件传递props的例子,但当需要交换数据的组件在组件树中距离较远时该怎么办呢?此时有几种选择:
- 继续通过props传递值,但这种方式在组件关系复杂时会变得繁琐,且会导致无关组件间的强耦合。
- 考虑使用React的上下文(Context)功能( https://reactjs.org/docs/context.html ),不过它会限制组件的可复用性,应谨慎使用。
- 使用状态管理解决方案,如Redux或MobX( https://github.com/mobxjs/mobx )。更多模式可参考: https://itnext.io/four-patterns-for-global-state-with-react-hooks-context-or-redux-cbc2dc787380

4. 渲染控制

在Vue.js和Angular中,需使用特定的模板语法来确定元素是否渲染、是否可见等。而在React中,由于主要使用JavaScript(或TypeScript)代码编写和操作组件,以编程方式控制渲染非常简单直观。借助JavaScript的强大功能,可将组件赋值给变量,使用 if-else 语句决定渲染内容和方式等。

以下是官方文档中的示例:

class LoginControl extends React.Component {
    render() {
        const isLoggedIn = this.state.isLoggedIn;
        let button;
        if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick} />;
        } else {
            button = <LoginButton onClick={this.handleLoginClick} />;
        }
        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />
                {button}
            </div>
        );
    }
}

在这个例子中,使用 if-else 表达式决定是否渲染登录或注销按钮。

由于JSX代码中可使用任何有效表达式,也可直接在JSX代码中嵌入逻辑表达式,如使用三元运算符:

render() {
    const isLoggedIn = this.state.isLoggedIn;
    return (
        <div>
            The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
        </div>
    );
}

更多技巧可参考React官方文档: https://reactjs.org/docs/conditional-rendering.html

5. 列表渲染

在Angular和Vue.js中,渲染列表需要特定的语法。为提高渲染效率,可给渲染列表中的每个元素分配唯一的键,以便Angular和Vue能轻松识别并仅重新渲染真正改变的部分。React也有相同的概念。

在React中渲染列表非常简单,因为可使用标准的JavaScript代码,将React元素赋值给变量或常量,可通过 map 操作遍历数组元素来构建组件。

以下是示例:

const membersList = [
    {id: 1, name: 'Sébastien'},
    {id: 2, name: 'Alexis'},
    {id: 3, name: 'John'},
    {id: 4, name: 'Doe'}
];
const memberElements = membersList.map((member) =>
    <li key={member.id}>{member.name}</li>
);

注意 key 属性,它能让React在DOM树中识别列表的特定元素。 key 的值应稳定且唯一,且这些值仅由React使用,不会添加到props对象中。更多关于 key 的信息可参考: https://medium.com/@adhithiravi/why-do-i-need-keys-in-react-lists-dbb522188bbb

下面是一个通过props接收列表并渲染的React组件示例:

function MembersList(props) {
    const members = props.members.map((member) =>
        <li key={member.id}>{member.name}</li>
    );
    return (
        <ul>{members}</ul>
    );
}
const membersList = [
    {id: 1, name: 'Sébastien'},
    {id: 2, name: 'Alexis'},
    {id: 3, name: 'John'},
    {id: 4, name: 'Doe'}
];
ReactDOM.render(
    <MembersList members={membersList} />,
    document.getElementById('root')
);

MembersList 组件期望接收一个成员数组作为props,然后进行渲染。将列表渲染到DOM只需调用 ReactDOM.render(...)

6. 内容投影/模板

在Vue和Angular中,组件可让其他组件提供子组件进行渲染。在Vue中,这一功能称为插槽,在Angular中称为内容投影或嵌入(该术语继承自AngularJS),React也有类似功能。

在React中,如果使用单个插槽(也称为空洞)来投影内容,通常使用名为 children 的prop。如果需要定义多个插槽,可自定义命名(如 header content footer 等)。

以下是单个插槽的示例:

// App.js
import React from 'react';
import './App.css';
import {Gruyere} from "./Gruyere";

function App() {
    return (
        <div className="App">
            <Gruyere>
                <h1>Hello</h1>
                <span>World</span>
            </Gruyere>
        </div>
    );
}
export default App;

// Gruyere.js
import React from 'react';

export class Gruyere extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                {this.props.children}
            </div>
        );
    }
}

Gruyere 组件简单地将传递的子元素作为其模板的一部分进行渲染。更多信息可参考: https://reactjs.org/docs/composition-vs-inheritance.html#containment

7. React钩子探索

过去,React类组件比函数组件功能更强大,类组件可以有内部状态、接入生命周期等,而函数组件没有等效的API,若要使用某些功能,需将函数组件转换为类组件,这显然不理想。

React钩子的引入填补了这一空白,它提供了简单的API,使函数组件能够处理状态、接入组件生命周期等。钩子不仅增强了函数组件的能力,还便于在组件间复用代码,尤其是有状态的逻辑。使用钩子可将组件拆分为更小但连贯的函数,避免类组件中相关逻辑分散在不同生命周期方法中的问题。需要注意的是,React钩子不能在类组件中使用。

通过降低函数组件的使用门槛,React团队旨在让新开发者更容易上手。因为类的概念相对复杂,开发者可能会过度追求面向对象,导致代码库变得复杂。钩子还可以组合使用,也能创建自定义钩子。

接下来,我们将介绍一些React内置的钩子。

8. 使用useState钩子定义状态

useState 是最常用的React钩子之一,它使函数组件能够拥有和使用内部状态,React会在组件重新渲染之间维护该内部状态。

在函数组件中调用 useState 钩子可定义一个状态变量和一个用于更新它的函数,也可在单个组件中多次调用 useState 来定义多个状态变量。

以下是一个简单的示例:

import React, {useState} from 'react';

export function Switch(props) {
    const [currentSwitchStatus, switchStatus] = useState(false);
    return (
        <div>
            <span>The switch is currently: {currentSwitchStatus? 'ON': 'OFF'} </span>
            <button onClick={() => switchStatus(!currentSwitchStatus)}>Change the value!</button>
        </div>
    );
}

在这个示例中,创建了一个 Switch 组件,默认开关是关闭的,点击按钮可改变其状态。关键代码是 const [currentSwitchStatus, switchStatus] = useState(false); ,传递给 useState 函数的值是新状态变量的初始值。 useState 函数返回一个元组,包含当前状态和用于更新状态的函数。

这里使用了ES2015的数组解构特性,后续会详细解释。在组件的JSX代码中,使用当前状态常量显示 ON OFF ,在按钮的 onClick 事件处理程序中调用状态更新函数来切换值。状态更新函数不会将新值合并到前一个值中,而是直接替换它,这体现了对不可变性的支持。

更多参考可查看: https://reactjs.org/docs/hooks-state.html

9. 数组解构

数组解构是ES2015的一个有用特性(TypeScript也支持),它与对象解构类似,但用于数组。

传统的代码可能如下:

const values = ['A', 'B', 'C'];
const first = values[0];
const second = values[1];
const third = values[2];

这种方式虽然可行,但代码冗长。使用数组解构可以更简洁地提取数组元素到其他变量或常量中:

const values = ['A', 'B', 'C'];
const [first, second, third] = values;

也可以提前定义要解构的变量,还能为提取的未定义值设置默认值:

const [first = 0, second = 1, third = 3] = ['A', undefined, 'B', 'C'];
console.log(second); // 1

数组解构还有一些有趣的技巧,例如不使用临时变量交换数组元素:

let x = 13;
let y = 37;
[x, y] = [y, x];
console.log(`${x} - ${y}`); // 37 - 13

还可以跳过数组元素进行解构:

const [x, , y] = [1, 2, 3]; // x === 1, y === 3

最后,还能提取数组的一部分值到变量中,将剩余的值提取到另一个变量中:

const values = ['A', 'B', 'C', 'D'];
const [first, ...allTheRest] = values;
console.log(allTheRest); // 'B', 'C', 'D'
10. 使用useEffect钩子处理副作用

useEffect 钩子是类组件React生命周期方法的函数式对应。它使函数组件能够定义在组件渲染或重新渲染后执行的逻辑。

以下是一个示例:

import React, {useState, useEffect} from 'react';

export function Switch(props) {
    const [currentSwitchStatus, switchStatus] = useState(false);
    useEffect(() => {
        alert('The switch has been activated. Hopefully, this was not by mistake :)');
    });
    return (
        <div>
            <span>The switch is currently: {currentSwitchStatus? 'ON': 'OFF'} </span>
            <button onClick={() => switchStatus(!currentSwitchStatus)}>Change the value!</button>
        </div>
    );
}

在这个示例中,调用了 useEffect 钩子并传递了一个函数,每当React更新该组件的DOM时(即每次渲染后),这个函数都会被调用,它实际上是前面提到的 componentDidMount componentDidUpdate componentWillUnmount 生命周期钩子的某种组合。

React开发全析:从生命周期到钩子函数

11. useEffect钩子的依赖项数组

在上面的 Switch 组件示例中, useEffect 每次组件渲染都会执行。但有时我们希望控制 useEffect 的执行时机,这时可以给 useEffect 传递第二个参数,即依赖项数组。

以下是一个带有依赖项数组的示例:

import React, {useState, useEffect} from 'react';

export function Switch(props) {
    const [currentSwitchStatus, switchStatus] = useState(false);
    useEffect(() => {
        alert('The switch status has changed!');
    }, [currentSwitchStatus]);
    return (
        <div>
            <span>The switch is currently: {currentSwitchStatus? 'ON': 'OFF'} </span>
            <button onClick={() => switchStatus(!currentSwitchStatus)}>Change the value!</button>
        </div>
    );
}

在这个例子中, useEffect 的第二个参数是 [currentSwitchStatus] ,这意味着只有当 currentSwitchStatus 的值发生变化时, useEffect 中的函数才会执行。如果依赖项数组为空 [] ,则 useEffect 只会在组件挂载时执行一次,类似于 componentDidMount

12. 使用useContext钩子进行上下文通信

前面提到远距离组件通信时可以考虑使用上下文(Context)功能,而 useContext 钩子可以让函数组件更方便地使用上下文。

首先,创建一个上下文对象:

import React from 'react';
const MyContext = React.createContext();
export default MyContext;

然后,在父组件中提供上下文值:

import React from 'react';
import MyContext from './MyContext';
import ChildComponent from './ChildComponent';

export function ParentComponent() {
    const contextValue = 'This is the context value';
    return (
        <MyContext.Provider value={contextValue}>
            <ChildComponent />
        </MyContext.Provider>
    );
}

最后,在子组件中使用 useContext 钩子获取上下文值:

import React, {useContext} from 'react';
import MyContext from './MyContext';

export function ChildComponent() {
    const value = useContext(MyContext);
    return (
        <div>
            <p>{value}</p>
        </div>
    );
}

通过这种方式,即使组件在组件树中距离较远,也可以方便地共享数据。

13. 使用useRef钩子

useRef 钩子可以创建一个可变的 ref 对象,它可以在组件的整个生命周期内保持不变。 ref 对象有一个 current 属性,可以存储任何值。

以下是一个使用 useRef 来访问DOM元素的示例:

import React, {useRef} from 'react';

export function InputComponent() {
    const inputRef = useRef(null);
    const handleClick = () => {
        inputRef.current.focus();
    };
    return (
        <div>
            <input ref={inputRef} type="text" />
            <button onClick={handleClick}>Focus the input</button>
        </div>
    );
}

在这个例子中, useRef 创建了一个 inputRef 对象,将其赋值给 input 元素的 ref 属性。当点击按钮时,通过 inputRef.current 可以访问到 input 元素,并调用其 focus 方法。

14. 自定义钩子

除了使用React提供的内置钩子,还可以创建自定义钩子。自定义钩子可以封装可复用的逻辑,提高代码的可维护性和复用性。

以下是一个自定义钩子的示例,用于记录按钮的点击次数:

import React, {useState} from 'react';

function useClickCounter() {
    const [count, setCount] = useState(0);
    const increment = () => {
        setCount(count + 1);
    };
    return [count, increment];
}

export function ClickCounterButton() {
    const [count, increment] = useClickCounter();
    return (
        <div>
            <p>You clicked the button {count} times.</p>
            <button onClick={increment}>Click me</button>
        </div>
    );
}

在这个例子中, useClickCounter 是一个自定义钩子,它封装了点击次数的状态和增加点击次数的逻辑。 ClickCounterButton 组件使用这个自定义钩子来实现点击计数的功能。

15. 钩子的使用规则

在使用React钩子时,需要遵循一些规则:
- 只在顶层调用钩子 :不要在循环、条件语句或嵌套函数中调用钩子,确保钩子总是以相同的顺序被调用。
- 只在函数组件或自定义钩子中调用钩子 :不要在普通的JavaScript函数中调用钩子。

以下是一个违反规则的示例:

import React, {useState} from 'react';

export function BadComponent(props) {
    if (props.condition) {
        const [value, setValue] = useState(0); // 错误:在条件语句中调用钩子
    }
    return <div>...</div>;
}

正确的做法是将钩子调用放在函数组件的顶层:

import React, {useState} from 'react';

export function GoodComponent(props) {
    const [value, setValue] = useState(0);
    if (props.condition) {
        // 使用value进行一些操作
    }
    return <div>...</div>;
}
16. 钩子的组合使用

钩子可以组合使用,以实现更复杂的功能。例如,结合 useState useEffect 来实现一个实时更新的时钟组件:

import React, {useState, useEffect} from 'react';

export function Clock() {
    const [time, setTime] = useState(new Date());
    useEffect(() => {
        const intervalId = setInterval(() => {
            setTime(new Date());
        }, 1000);
        return () => {
            clearInterval(intervalId);
        };
    }, []);
    return (
        <div>
            <p>{time.toLocaleTimeString()}</p>
        </div>
    );
}

在这个例子中, useState 用于存储当前时间, useEffect 用于每秒更新一次时间。 useEffect 返回一个清理函数,用于在组件卸载时清除定时器,避免内存泄漏。

17. React钩子的优势总结

React钩子为函数组件带来了很多优势,总结如下:
| 优势 | 说明 |
| — | — |
| 状态管理 | 让函数组件能够方便地管理内部状态,避免了将函数组件转换为类组件的麻烦。 |
| 生命周期管理 | 提供了类似于类组件生命周期方法的功能,如 useEffect 可以模拟 componentDidMount componentDidUpdate componentWillUnmount 。 |
| 代码复用 | 便于复用有状态的逻辑,通过自定义钩子可以将通用逻辑封装起来,提高代码的可维护性和复用性。 |
| 降低学习门槛 | 对于新开发者来说,函数组件和钩子的概念相对简单,避免了类和对象的复杂使用。 |

18. 总结

通过对React组件的生命周期、事件处理、渲染控制、列表渲染、内容投影以及各种钩子的学习,我们了解到React提供了丰富的功能和灵活的开发方式。从传统的类组件到现代的函数组件和钩子,React不断地在进化,让开发者能够更高效地构建用户界面。

在实际开发中,我们可以根据具体的需求选择合适的方式来开发组件。对于简单的组件,函数组件和钩子可能是更好的选择;而对于复杂的业务逻辑,类组件仍然有其用武之地。同时,合理使用各种钩子可以让代码更加简洁、可维护和可复用。

希望本文能够帮助你更好地理解和使用React,在开发过程中更加得心应手。

下面是一个简单的mermaid流程图,展示了 useEffect 钩子的执行流程:

graph TD;
    A[组件渲染] --> B{是否有依赖项数组};
    B -- 无 --> C[执行useEffect函数];
    B -- 有 --> D{依赖项是否变化};
    D -- 是 --> C;
    D -- 否 --> E[不执行useEffect函数];

通过这个流程图,我们可以更清晰地看到 useEffect 钩子在不同情况下的执行逻辑。

内容概要:本文详细介绍了一个基于Java和Vue的联邦学习隐私保护推荐系统的设计与实现。系统采用联邦学习架构,使用户数据在本地完成模型训练,仅上传加密后的模型参数或梯度,通过中心服务器进行联邦平均聚合,从而实现数据隐私保护与协同建模的双重目标。项目涵盖完整的系统架构设计,包括本地模型训练、中心参数聚合、安全通信、前后端解耦、推荐算法插件化等模块,并结合差分隐私与同态加密等技术强化安全性。同时,系统通过Vue前端实现用户行为采集与个性化推荐展示,Java后端支撑高并发服务与日志处理,形成“本地训练—参数上传—全局聚合—模型下发—个性化微调”的完整闭环。文中还提供了关键模块的代码示例,如特征提取、模型聚合、加密上传等,增强了项目的可实施性与工程参考价值。 适合人群:具备一定Java和Vue开发基础,熟悉Spring Boot、RESTful API、分布式系统或机器学习相关技术,从事推荐系统、隐私计算或全栈开发方向的研发人员。 使用场景及目标:①学习联邦学习在推荐系统中的工程落地方法;②掌握隐私保护机制(如加密传输、差分隐私)与模型聚合技术的集成;③构建高安全、可扩展的分布式推荐系统原型;④实现前后端协同的个性化推荐闭环系统。 阅读建议:建议结合代码示例深入理解联邦学习流程,重点关注本地训练与全局聚合的协同逻辑,同时可基于项目架构进行算法替换与功能扩展,适用于科研验证与工业级系统原型开发
源码来自:https://pan.quark.cn/s/a4b39357ea24 遗传算法 - 简书 遗传算法的理论是根据达尔文进化论而设计出来的算法: 人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。 遗传算法(英语:genetic algorithm (GA) )是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择、杂交等。 搜索算法的共同特征为: 首先组成一组候选解 依据某些适应性条件测算这些候选解的适应度 根据适应度保留某些候选解,放弃其他候选解 对保留的候选解进行某些操作,生成新的候选解 遗传算法流程 遗传算法的一般步骤 my_fitness函数 评估每条染色体所对应个体的适应度 升序排列适应度评估值,选出 前 parent_number 个 个体作为 待选 parent 种群(适应度函数的值越小越好) 从 待选 parent 种群 中随机选择 2 个个体作为父方和母方。 抽取父母双方的染色体,进行交叉,产生 2 个子代。 (交叉概率) 对子代(parent + 生成的 child)的染色体进行变异。 (变异概率) 重复3,4,5步骤,直到新种群(parentnumber + childnumber)的产生。 循环以上步骤直至找到满意的解。 名词解释 交叉概率:两个个体进行交配的概率。 例如,交配概率为0.8,则80%的“夫妻”会生育后代。 变异概率:所有的基因中发生变异的占总体的比例。 GA函数 适应度函数 适应度函数由解决的问题决定。 举一个平方和的例子。 简单的平方和问题 求函数的最小值,其中每个变量的取值区间都是 [-1, ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值