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

React 中 State 和 Props 的区别与联系

2021-11-113.3k 阅读

React 中 State 的基本概念

在 React 应用中,state 是组件内部的一个对象,用于存储组件的可变数据。它代表了组件的状态,并且当 state 发生变化时,React 会自动重新渲染组件,从而更新用户界面。

state 只能在类组件中使用,函数组件在 React Hooks 出现之前没有自己的 state。使用 state 能够让组件根据不同的状态展示不同的 UI。例如,一个简单的计数器组件,通过 state 来存储当前的计数数值。

1. State 的初始化

在类组件中,通过在 constructor 方法中使用 this.state 来初始化 state。如下代码展示了一个简单的计数器组件初始化 state 的过程:

import React, { Component } from 'react';

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

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

export default Counter;

在上述代码中,Counter 组件在 constructor 中初始化了 statestate 中有一个属性 count 初始值为 0。并且在 render 方法中展示了 count 的值,并提供了一个按钮,点击按钮可以增加 count 的值。

2. State 的更新

更新 state 不能直接修改 this.state,而需要使用 setState 方法。这是因为直接修改 this.state 不会触发组件的重新渲染,而 setState 方法会合并新的 state 并触发重新渲染。

继续以上述计数器为例,点击按钮时调用 this.setState 方法来更新 count 的值:

<button onClick={() => this.setState({ count: this.state.count + 1 })}>Increment</button>

setState 方法接受一个对象作为参数,这个对象中的属性会合并到当前的 state 中。例如,如果 state 还有其他属性,如 isLoading,在更新 count 时不会影响 isLoading

class AnotherComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
            isLoading: false
        };
    }

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

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <p>Loading: {this.state.isLoading ? 'Yes' : 'No'}</p>
                <button onClick={this.increment}>Increment</button>
            </div>
        );
    }
}

在这个例子中,点击按钮只更新 countisLoading 保持不变。

React 中 Props 的基本概念

props(properties 的缩写)是 React 组件接收外部数据的方式。它是单向流动的,即从父组件传递到子组件,子组件不能直接修改 props

props 可以传递各种类型的数据,包括基本数据类型(如字符串、数字)、对象、函数等。这使得组件具有更高的可复用性和灵活性。

1. Props 的传递

在父组件中,通过在子组件标签上以属性的形式传递 props。例如,有一个 Child 组件,父组件 Parent 向其传递一个 name 属性:

import React, { Component } from 'react';

class Child extends Component {
    render() {
        return <p>Hello, {this.props.name}!</p>;
    }
}

class Parent extends Component {
    render() {
        return (
            <div>
                <Child name="John" />
            </div>
        );
    }
}

export default Parent;

在上述代码中,Parent 组件在渲染 Child 组件时,传递了 name 属性值为 JohnChild 组件通过 this.props.name 来获取这个值并展示。

2. Props 的默认值

可以为组件的 props 设置默认值。在 ES6 类组件中,通过在组件类上设置 defaultProps 属性来实现。例如:

class Greeting extends Component {
    render() {
        return <p>Hello, {this.props.name}!</p>;
    }
}

Greeting.defaultProps = {
    name: 'Guest'
};

class App extends Component {
    render() {
        return (
            <div>
                <Greeting />
                <Greeting name="Jane" />
            </div>
        );
    }
}

在这个例子中,Greeting 组件设置了 name 的默认值为 Guest。当父组件没有传递 name 属性时,Greeting 组件会使用默认值。当父组件传递了 name 属性(如 name="Jane"),则使用传递的值。

State 和 Props 的区别

1. 数据流向

  • State:是组件内部的状态,数据在组件内部流动和变化。组件自身可以通过 setState 方法来更新 state,并且这种更新会触发组件的重新渲染。例如前面的计数器组件,点击按钮在组件内部更新 state 中的 count 值。
  • Props:数据是从父组件传递到子组件,是单向流动的。子组件只能接收和使用 props,不能直接修改它们。如果需要修改,必须通过父组件来进行。例如 Child 组件只能使用父组件传递的 name 属性,不能直接在 Child 组件内部修改 this.props.name

2. 可变性

  • State:是可变的,组件可以通过 setState 方法来修改 state 的值。这种可变性使得组件能够根据不同的用户操作或其他事件来更新自身的状态,从而展示不同的 UI。
  • Props:是不可变的,子组件不能直接改变接收到的 props。如果 props 需要改变,必须由父组件重新传递新的值。这确保了数据流动的单向性和可预测性。

3. 作用范围

  • State:只在定义它的组件内部起作用。一个组件的 state 对其他组件是不可见的,除非通过 props 传递给其他组件。例如,一个 Form 组件的 state 用于管理表单的输入状态,其他组件无法直接访问这个 state
  • Props:用于组件之间的通信,主要是从父组件到子组件。它可以跨越多个组件层次进行传递,使得不同组件之间能够共享数据。例如,一个 App 组件可以通过 props 将用户信息传递给深层嵌套的 UserProfile 组件。

4. 初始化方式

  • State:在类组件的 constructor 方法中通过 this.state 进行初始化,并且可以根据组件的需求设置初始值。例如计数器组件在 constructor 中初始化 count 为 0。
  • Props:由父组件在渲染子组件时以属性的形式传递给子组件,子组件本身并不负责初始化 props,而是接收父组件传递的值。

5. 触发重新渲染

  • State:当调用 setState 方法更新 state 时,会触发组件的重新渲染,React 会对比前后的 state 差异,只更新发生变化的部分 DOM。例如计数器组件每次点击按钮更新 count 后,整个组件会重新渲染,展示新的 count 值。
  • Props:当父组件传递给子组件的 props 发生变化时,子组件会重新渲染。例如父组件根据某个条件改变了传递给子组件的 name 属性值,子组件会重新渲染以展示新的 name

State 和 Props 的联系

1. Props 初始化 State

在很多情况下,组件的 state 可以使用父组件传递的 props 来进行初始化。例如,一个 UserProfile 组件可能从父组件接收 user 对象作为 props,然后在组件内部将 user 的某些属性初始化到 state 中。

class UserProfile extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: props.user.username,
            age: props.user.age
        };
    }

    render() {
        return (
            <div>
                <p>Username: {this.state.username}</p>
                <p>Age: {this.state.age}</p>
            </div>
        );
    }
}

class App extends Component {
    render() {
        const user = {
            username: 'Alice',
            age: 25
        };
        return (
            <div>
                <UserProfile user={user} />
            </div>
        );
    }
}

在这个例子中,UserProfile 组件使用父组件传递的 user 对象中的 usernameage 属性初始化了自身的 state

2. State 影响 Props 传递

组件的 state 变化可以导致父组件传递给其他子组件的 props 发生变化。例如,一个 Filter 组件通过 state 控制过滤条件,父组件根据 Filter 组件的 state 来决定传递给 List 组件什么样的 props 数据。

class Filter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            filterText: ''
        };

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({
            filterText: event.target.value
        });
    }

    render() {
        return (
            <input
                type="text"
                placeholder="Filter"
                value={this.state.filterText}
                onChange={this.handleChange}
            />
        );
    }
}

class List extends Component {
    render() {
        const filteredItems = this.props.items.filter(item => item.includes(this.props.filterText));
        return (
            <ul>
                {filteredItems.map((item, index) => (
                    <li key={index}>{item}</li>
                ))}
            </ul>
        );
    }
}

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: ['apple', 'banana', 'cherry']
        };
    }

    render() {
        return (
            <div>
                <Filter />
                <List items={this.state.items} filterText={this.state.filterText} />
            </div>
        );
    }
}

在这个例子中,Filter 组件的 state 中的 filterText 发生变化时,虽然 List 组件自身没有直接修改 props,但是父组件 App 会根据 Filter 组件的 state 变化重新传递 filterTextList 组件,从而影响 List 组件展示的数据。

3. 共同维护组件树状态

stateprops 共同作用于维护整个 React 组件树的状态。props 负责将数据从父组件传递到子组件,而 state 则管理组件内部的可变状态。通过这种方式,React 能够高效地更新和渲染组件,保持用户界面与数据状态的一致性。例如,在一个复杂的电商应用中,props 可以传递商品列表数据,而各个商品组件内部的 state 可以管理商品的选中状态、数量等。当用户进行操作(如添加商品到购物车)时,组件的 state 发生变化,通过 props 的传递和组件之间的通信,整个应用的状态得到更新,用户界面也相应地发生改变。

使用场景分析

1. State 的使用场景

  • 用户交互状态:如表单输入、按钮点击状态等。例如,一个登录表单组件,通过 state 来管理用户名、密码的输入值以及提交按钮的禁用状态。
class LoginForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: '',
            password: '',
            isButtonDisabled: true
        };

        this.handleUsernameChange = this.handleUsernameChange.bind(this);
        this.handlePasswordChange = this.handlePasswordChange.bind(this);
    }

    handleUsernameChange(event) {
        const newUsername = event.target.value;
        this.setState({
            username: newUsername,
            isButtonDisabled: newUsername.length === 0 || this.state.password.length === 0
        });
    }

    handlePasswordChange(event) {
        const newPassword = event.target.value;
        this.setState({
            password: newPassword,
            isButtonDisabled: newPassword.length === 0 || this.state.username.length === 0
        });
    }

    render() {
        return (
            <form>
                <label>Username:</label>
                <input
                    type="text"
                    value={this.state.username}
                    onChange={this.handleUsernameChange}
                />
                <label>Password:</label>
                <input
                    type="password"
                    value={this.state.password}
                    onChange={this.handlePasswordChange}
                />
                <button disabled={this.state.isButtonDisabled}>Login</button>
            </form>
        );
    }
}
  • 组件内部状态管理:例如一个图片轮播组件,通过 state 来管理当前显示的图片索引。
class ImageSlider extends Component {
    constructor(props) {
        super(props);
        this.state = {
            currentIndex: 0
        };

        this.nextImage = this.nextImage.bind(this);
    }

    nextImage() {
        this.setState({
            currentIndex: (this.state.currentIndex + 1) % this.props.images.length
        });
    }

    render() {
        const { images } = this.props;
        const currentImage = images[this.state.currentIndex];
        return (
            <div>
                <img src={currentImage.src} alt={currentImage.alt} />
                <button onClick={this.nextImage}>Next</button>
            </div>
        );
    }
}

2. Props 的使用场景

  • 组件复用与定制:通过传递不同的 props 来定制组件的行为和外观。例如,一个通用的 Button 组件,可以通过 props 传递不同的文本、颜色、大小等属性来满足不同的需求。
class Button extends Component {
    render() {
        const { text, color, size } = this.props;
        const style = {
            backgroundColor: color,
            fontSize: size
        };
        return (
            <button style={style}>{text}</button>
        );
    }
}

class App extends Component {
    render() {
        return (
            <div>
                <Button text="Primary" color="blue" size="16px" />
                <Button text="Secondary" color="gray" size="14px" />
            </div>
        );
    }
}
  • 数据传递与共享:在组件树中传递数据。例如,一个 App 组件有多个子组件,App 组件可以通过 props 将用户信息传递给需要的子组件,如 UserInfo 组件。
class UserInfo extends Component {
    render() {
        const { user } = this.props;
        return (
            <div>
                <p>Name: {user.name}</p>
                <p>Email: {user.email}</p>
            </div>
        );
    }
}

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            user: {
                name: 'Bob',
                email: 'bob@example.com'
            }
        };
    }

    render() {
        return (
            <div>
                <UserInfo user={this.state.user} />
            </div>
        );
    }
}

最佳实践与注意事项

1. State 相关

  • 避免不必要的 State:不要将可以通过 props 或其他数据计算得出的值放入 state。例如,如果有一个组件显示当前时间,不需要将时间存储在 state 中,而是每次渲染时获取当前时间。
class TimeDisplay extends Component {
    render() {
        const currentTime = new Date().toLocaleTimeString();
        return <p>Current time: {currentTime}</p>;
    }
}
  • 合理使用 setState 的回调setState 是异步的,在某些情况下,需要在 state 更新后执行一些操作,可以使用 setState 的回调函数。例如,在更新 state 后更新 DOM:
class ScrollComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            scrollToTop: false
        };
    }

    handleClick = () => {
        this.setState({
            scrollToTop: true
        }, () => {
            window.scrollTo(0, 0);
        });
    }

    render() {
        return (
            <div>
                <button onClick={this.handleClick}>Scroll to top</button>
            </div>
        );
    }
}
  • 正确处理 state 更新:由于 setState 是合并操作,在更新 state 时要注意不要覆盖其他属性。如果 state 是一个复杂对象,最好使用展开运算符来更新。例如:
class ComplexStateComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            user: {
                name: 'Eve',
                age: 30,
                address: {
                    city: 'New York'
                }
            }
        };
    }

    updateUserAge = () => {
        this.setState(prevState => ({
            user: {
               ...prevState.user,
                age: prevState.user.age + 1
            }
        }));
    }

    render() {
        return (
            <div>
                <p>User age: {this.state.user.age}</p>
                <button onClick={this.updateUserAge}>Increment age</button>
            </div>
        );
    }
}

2. Props 相关

  • 验证 Props:使用 PropTypes(在 React v15 及之前版本)或 TypeScript 进行 props 类型和值的验证。这可以帮助在开发过程中发现传递 props 时的错误。例如,使用 PropTypes
import React, { Component } from'react';
import PropTypes from 'prop-types';

class MyComponent extends Component {
    render() {
        return <p>{this.props.message}</p>;
    }
}

MyComponent.propTypes = {
    message: PropTypes.string.isRequired
};
  • 避免深层嵌套传递 Props:如果 props 需要传递经过多个组件层次,可以考虑使用 Context(React 提供的一种共享数据方式)或者状态管理库(如 Redux)。例如,在一个多层嵌套的组件结构中,如果最底层的组件需要某个来自顶层组件的 props,层层传递 props 会使代码变得繁琐。使用 Context 可以更方便地共享数据。
  • 保持 Props 简洁:传递给组件的 props 应该尽量简洁,避免传递过多无关的数据。这样可以提高组件的可读性和可维护性。例如,如果一个 Button 组件只需要文本和点击事件处理函数,就不要传递其他无关的样式属性,除非确实有必要。

总结

在 React 开发中,stateprops 是两个核心概念。state 负责管理组件内部的可变状态,通过 setState 方法更新,驱动组件重新渲染。props 则用于组件之间的数据传递,特别是从父组件到子组件,保证了数据的单向流动和可预测性。

理解它们的区别与联系对于编写高效、可维护的 React 应用至关重要。合理运用 stateprops,遵循最佳实践,能够让我们构建出更加健壮和灵活的 React 组件和应用程序。在实际开发中,要根据具体的需求和场景,准确地使用 stateprops,避免滥用或误用,从而提高开发效率和代码质量。无论是简单的 UI 组件还是复杂的应用逻辑,掌握好 stateprops 都是 React 开发的关键。通过不断实践和优化,开发者可以更好地利用这两个特性,创造出优秀的用户界面和交互体验。