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

React 组件化开发入门指南

2021-06-214.1k 阅读

一、React 组件化开发基础概念

在 React 开发中,组件是核心概念。组件就像是一个个独立的、可复用的代码块,它们各自管理自己的状态和逻辑,并且可以组合起来构建复杂的用户界面。

(一)函数式组件

函数式组件是最简单的一种组件形式,它本质上就是 JavaScript 函数。函数接收一个 props 对象作为参数,并返回一个 React 元素。例如:

import React from'react';

const Welcome = (props) => {
    return <div>Hello, {props.name}</div>;
};

export default Welcome;

在上述代码中,Welcome 组件接收一个 props 参数,其中包含 name 属性,然后在返回的 div 元素中显示欢迎信息。

函数式组件没有自己的状态(state),它们是无状态组件。这种组件的优点是简单、纯粹,只依赖于传入的 props 来渲染,易于测试和理解。

(二)类组件

类组件是通过 ES6 的 class 语法来定义的。相比函数式组件,类组件可以拥有自己的状态和生命周期方法。下面是一个简单的类组件示例:

import React, { Component } from'react';

class Counter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    increment = () => {
        this.setState({
            count: this.state.count + 1
        });
    };

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.increment}>Increment</button>
            </div>
        );
    }
}

export default Counter;

在这个 Counter 组件中,通过 constructor 方法初始化了组件的状态 countincrement 方法用于更新状态,通过 setState 方法触发组件的重新渲染。render 方法返回了组件的 JSX 结构,展示当前的计数和一个增加计数的按钮。

类组件由于拥有状态和生命周期方法,适合处理较为复杂的业务逻辑,例如需要与服务器进行交互、处理定时器等场景。

二、组件的属性(Props)

(一)Props 的传递

Props 是父组件向子组件传递数据的方式。在 React 中,数据是单向流动的,即从父组件流向子组件。例如,有一个父组件 App 和子组件 Greeting

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

const Greeting = (props) => {
    return <div>Hello, {props.user}</div>;
};

export default Greeting;

// App.js
import React from'react';
import Greeting from './Greeting';

const App = () => {
    const user = 'John';
    return <Greeting user={user} />;
};

export default App;

App 组件中,通过 user={user}user 变量的值传递给了 Greeting 组件,Greeting 组件通过 props.user 来接收这个值并显示。

(二)Props 的类型检查

为了确保组件接收到的 props 类型正确,React 提供了 prop-types 库。首先安装 prop-types

npm install prop-types --save

然后在组件中使用:

import React from'react';
import PropTypes from 'prop-types';

const Greeting = (props) => {
    return <div>Hello, {props.user}</div>;
};

Greeting.propTypes = {
    user: PropTypes.string.isRequired
};

export default Greeting;

在上述代码中,通过 Greeting.propTypes 定义了 user 属性必须是字符串类型且是必填的。如果父组件传递的 user 不是字符串类型,React 开发环境会在控制台给出警告。

三、组件的状态(State)

(一)状态的概念与使用

状态是组件内部维护的数据,它可以随着用户交互或其他事件发生变化,从而导致组件重新渲染。以之前的 Counter 类组件为例,count 就是组件的状态。

在函数式组件中,从 React 16.8 开始引入了 useState Hook 来添加状态。例如:

import React, { useState } from'react';

const Counter = () => {
    const [count, setCount] = useState(0);

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

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
};

export default Counter;

useState 接收一个初始值(这里是 0),并返回一个数组,数组的第一个元素是当前状态值(count),第二个元素是用于更新状态的函数(setCount)。

(二)状态更新的注意事项

  1. 不要直接修改状态:在类组件中,不要直接修改 this.state,例如 this.state.count = 1 这种方式是错误的,必须使用 setState 方法。在函数式组件中,要使用 setCount 这样的更新函数。
  2. 状态更新是异步的:在类组件中,setState 是异步的。例如:
this.setState({
    count: this.state.count + 1
});
console.log(this.state.count); // 这里打印的可能还是旧值

如果需要在状态更新后执行某些操作,可以使用 setState 的回调函数:

this.setState({
    count: this.state.count + 1
}, () => {
    console.log(this.state.count); // 这里可以获取到更新后的状态值
});

在函数式组件中,虽然 setCount 看起来是同步的,但 React 内部也是进行了优化处理,所以也不应该依赖更新后立即获取最新值。

四、组件的生命周期

(一)类组件的生命周期

  1. 挂载阶段
    • constructor:组件实例化时调用,用于初始化状态和绑定方法。例如:
class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            data: []
        };
        this.fetchData = this.fetchData.bind(this);
    }
    //...
}
- **componentDidMount**:组件挂载到 DOM 后调用,通常用于发起网络请求、添加事件监听器等操作。例如:
componentDidMount() {
    fetch('https://example.com/api/data')
      .then(response => response.json())
      .then(data => this.setState({ data }));
}
  1. 更新阶段
    • shouldComponentUpdate:在组件接收到新的 propsstate 时调用,返回一个布尔值,用于决定组件是否需要更新。可以通过比较新旧 propsstate 来进行性能优化。例如:
shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.data!== this.props.data) {
        return true;
    }
    return false;
}
- **componentDidUpdate**:组件更新后调用,可用于在组件更新后执行一些副作用操作,例如更新 DOM 元素的样式。

3. 卸载阶段: - componentWillUnmount:组件从 DOM 中移除前调用,通常用于清理定时器、取消网络请求等操作。例如:

componentWillUnmount() {
    clearInterval(this.timer);
}

(二)函数式组件与 Hook 的生命周期等效

在函数式组件中,虽然没有传统的生命周期方法,但可以通过 useEffect Hook 来模拟部分生命周期行为。

  1. 模拟 componentDidMountcomponentDidUpdate
import React, { useEffect } from'react';

const MyComponent = () => {
    const [data, setData] = useState([]);

    useEffect(() => {
        fetch('https://example.com/api/data')
          .then(response => response.json())
          .then(data => setData(data));
    }, []);

    return (
        <div>
            {/* 渲染数据 */}
        </div>
    );
};

export default MyComponent;

在上述代码中,useEffect 接收一个回调函数,第二个参数是一个依赖数组 []。当依赖数组为空时,useEffect 只在组件挂载后执行一次,相当于 componentDidMount。如果依赖数组中有变量,例如 [props.value],那么当 props.value 变化时,useEffect 会在组件更新后执行,相当于 componentDidUpdate。 2. 模拟 componentWillUnmountuseEffect 的回调函数可以返回一个清理函数,这个清理函数会在组件卸载时执行,相当于 componentWillUnmount。例如:

import React, { useEffect } from'react';

const MyComponent = () => {
    useEffect(() => {
        const timer = setInterval(() => {
            console.log('Timer is running');
        }, 1000);

        return () => {
            clearInterval(timer);
        };
    }, []);

    return (
        <div>
            {/* 组件内容 */}
        </div>
    );
};

export default MyComponent;

五、组件的组合与嵌套

(一)组件组合

组件组合是将多个小的组件组合成一个更大的组件。例如,有一个 Button 组件和一个 Form 组件,Form 组件可以包含多个 Button 组件:

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

const Button = (props) => {
    return <button>{props.label}</button>;
};

export default Button;

// Form.js
import React from'react';
import Button from './Button';

const Form = () => {
    return (
        <form>
            <Button label="Submit" />
            <Button label="Cancel" />
        </form>
    );
};

export default Form;

Form 组件中,通过引入并使用 Button 组件,实现了组件的组合,使代码更加模块化和可维护。

(二)组件嵌套

组件嵌套是指在一个组件的 JSX 结构中包含其他组件,并且可以传递不同层次的 props。例如,有一个 Parent 组件,它包含一个 Child 组件,Child 组件又包含一个 GrandChild 组件:

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

const GrandChild = (props) => {
    return <div>{props.message}</div>;
};

export default GrandChild;

// Child.js
import React from'react';
import GrandChild from './GrandChild';

const Child = (props) => {
    return (
        <div>
            <GrandChild message={props.childMessage} />
        </div>
    );
};

export default Child;

// Parent.js
import React from'react';
import Child from './Child';

const Parent = () => {
    const message = 'Hello from Parent';
    return (
        <div>
            <Child childMessage={message} />
        </div>
    );
};

export default Parent;

在这个例子中,Parent 组件将 message 传递给 Child 组件,Child 组件再将其传递给 GrandChild 组件,展示了组件嵌套和 props 传递的过程。

六、高阶组件(HOC)

(一)高阶组件的概念

高阶组件是一个函数,它接收一个组件作为参数,并返回一个新的组件。高阶组件常用于复用组件逻辑,例如添加日志记录、权限验证等功能。

(二)高阶组件的实现示例

下面是一个简单的高阶组件示例,用于添加日志记录功能:

import React from'react';

const withLogging = (WrappedComponent) => {
    return (props) => {
        console.log('Component will render');
        return <WrappedComponent {...props} />;
    };
};

export default withLogging;

使用这个高阶组件:

import React from'react';
import withLogging from './withLogging';

const MyComponent = (props) => {
    return <div>{props.message}</div>;
};

const LoggedComponent = withLogging(MyComponent);

export default LoggedComponent;

在上述代码中,withLogging 接收 MyComponent 作为参数,并返回一个新的组件 LoggedComponent。当 LoggedComponent 渲染时,会先在控制台打印日志信息。

七、React 组件的性能优化

(一)使用 React.memoshouldComponentUpdate

  1. React.memo 用于函数式组件React.memo 是一个高阶组件,它可以对函数式组件进行性能优化。它会浅比较组件的 props,如果 props 没有变化,组件就不会重新渲染。例如:
import React from'react';

const MyComponent = React.memo((props) => {
    return <div>{props.value}</div>;
});

export default MyComponent;
  1. shouldComponentUpdate 用于类组件: 如前文所述,在类组件中可以通过 shouldComponentUpdate 方法来控制组件是否更新。通过比较新旧 propsstate,只有在必要时才返回 true 触发更新,从而避免不必要的渲染。

(二)虚拟 DOM 与 Diff 算法

React 使用虚拟 DOM 来提高性能。当组件状态或 props 发生变化时,React 会创建一个新的虚拟 DOM 树,并与之前的虚拟 DOM 树进行比较,这个比较过程使用了 Diff 算法。Diff 算法会找出最小的变化集,并将这些变化应用到实际的 DOM 上,而不是重新渲染整个页面。

例如,有一个列表组件,当其中一个列表项的数据发生变化时,React 通过 Diff 算法只会更新该列表项对应的 DOM 元素,而不是整个列表。

八、实战案例:构建一个简单的待办事项应用

(一)功能需求分析

  1. 用户可以输入待办事项内容。
  2. 可以添加待办事项到列表中。
  3. 可以标记待办事项为已完成或未完成。
  4. 可以删除待办事项。

(二)组件设计

  1. Input 组件:用于用户输入待办事项内容。
  2. Button 组件:用于触发添加待办事项的操作。
  3. TodoItem 组件:展示单个待办事项,包含完成状态、内容和删除按钮。
  4. TodoList 组件:管理所有的待办事项列表。

(三)代码实现

  1. Button.js
import React from'react';

const Button = (props) => {
    return <button onClick={props.onClick}>{props.label}</button>;
};

export default Button;
  1. TodoItem.js
import React from'react';
import Button from './Button';

const TodoItem = (props) => {
    const { item, onToggle, onDelete } = props;
    return (
        <li>
            <input type="checkbox" checked={item.completed} onChange={onToggle} />
            <span style={{ textDecoration: item.completed? 'line-through' : 'none' }}>{item.text}</span>
            <Button label="Delete" onClick={() => onDelete(item.id)} />
        </li>
    );
};

export default TodoItem;
  1. Input.js
import React, { useState } from'react';

const Input = (props) => {
    const [inputValue, setInputValue] = useState('');

    const handleChange = (e) => {
        setInputValue(e.target.value);
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        props.onSubmit(inputValue);
        setInputValue('');
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" value={inputValue} onChange={handleChange} />
            <Button label="Add" />
        </form>
    );
};

export default Input;
  1. TodoList.js
import React, { useState } from'react';
import Input from './Input';
import TodoItem from './TodoItem';

const TodoList = () => {
    const [todos, setTodos] = useState([]);

    const addTodo = (text) => {
        const newTodo = {
            id: Date.now(),
            text,
            completed: false
        };
        setTodos([...todos, newTodo]);
    };

    const toggleTodo = (id) => {
        setTodos(todos.map(todo =>
            todo.id === id? { ...todo, completed:!todo.completed } : todo
        ));
    };

    const deleteTodo = (id) => {
        setTodos(todos.filter(todo => todo.id!== id));
    };

    return (
        <div>
            <h1>To - Do List</h1>
            <Input onSubmit={addTodo} />
            <ul>
                {todos.map(todo => (
                    <TodoItem
                        key={todo.id}
                        item={todo}
                        onToggle={() => toggleTodo(todo.id)}
                        onDelete={deleteTodo}
                    />
                ))}
            </ul>
        </div>
    );
};

export default TodoList;

通过以上组件的组合和逻辑实现,完成了一个简单的待办事项应用。在实际开发中,可以进一步对该应用进行样式设计、功能扩展等优化。

通过以上对 React 组件化开发的全面介绍,从基础概念到实战案例,希望读者能够对 React 组件化开发有更深入的理解和掌握,从而在前端开发项目中更好地运用 React 技术。