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

React Props 和 State 的基础入门

2024-09-132.2k 阅读

React Props 基础入门

什么是 Props

在 React 中,Props(Properties 的缩写)是一种从父组件向子组件传递数据的方式。可以将 Props 看作是子组件的输入参数,就像函数的参数一样。通过传递 Props,父组件可以定制子组件的外观和行为。

Props 是只读的,这意味着子组件不能直接修改接收到的 Props。如果需要修改数据,通常需要在父组件中更新状态,然后通过 Props 重新传递新的数据给子组件。

传递 Props

假设我们有一个父组件 App 和一个子组件 GreetingGreeting 组件需要一个 name 属性来显示个性化的问候语。

首先,创建 Greeting 组件:

import React from'react';

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

export default Greeting;

在这个组件中,props 是一个对象,包含了父组件传递过来的所有属性。我们通过 props.name 来获取传递的 name 值。

然后,在 App 组件中使用 Greeting 组件并传递 name 属性:

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

const App = () => {
  return (
    <div>
      <Greeting name="John" />
    </div>
  );
};

export default App;

在上述代码中,App 组件将 name 属性设置为 John 传递给了 Greeting 组件。Greeting 组件接收到这个 props 后,会在页面上显示 Hello, John!

传递多种类型的 Props

Props 不仅可以传递字符串,还可以传递数字、布尔值、数组、对象甚至函数。

  1. 传递数字: 假设 Greeting 组件还需要一个 age 属性来显示年龄信息。修改 Greeting 组件如下:
import React from'react';

const Greeting = (props) => {
  return (
    <div>
      Hello, {props.name}! You are {props.age} years old.
    </div>
  );
};

export default Greeting;

App 组件中传递 age 属性:

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

const App = () => {
  return (
    <div>
      <Greeting name="John" age={25} />
    </div>
  );
};

export default App;

这里 age 属性被设置为数字 25 传递给 Greeting 组件,页面将显示 Hello, John! You are 25 years old.

  1. 传递布尔值: 假设 Greeting 组件需要一个 isLoggedIn 属性来决定是否显示额外的信息。修改 Greeting 组件如下:
import React from'react';

const Greeting = (props) => {
  return (
    <div>
      Hello, {props.name}!
      {props.isLoggedIn && <p>You are logged in.</p>}
    </div>
  );
};

export default Greeting;

App 组件中传递 isLoggedIn 属性:

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

const App = () => {
  return (
    <div>
      <Greeting name="John" isLoggedIn={true} />
    </div>
  );
};

export default App;

isLoggedIntrue 时,You are logged in. 这行文本会显示在页面上。

  1. 传递数组: 假设 Greeting 组件需要一个 hobbies 属性来显示用户的爱好列表。修改 Greeting 组件如下:
import React from'react';

const Greeting = (props) => {
  return (
    <div>
      Hello, {props.name}! Your hobbies are:
      <ul>
        {props.hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
    </div>
  );
};

export default Greeting;

App 组件中传递 hobbies 属性:

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

const App = () => {
  return (
    <div>
      <Greeting name="John" hobbies={['reading', 'painting', 'coding']} />
    </div>
  );
};

export default App;

这样,Greeting 组件会将用户的爱好以列表形式显示在页面上。

  1. 传递对象: 假设 Greeting 组件需要一个 userInfo 对象属性,包含更多用户信息。修改 Greeting 组件如下:
import React from'react';

const Greeting = (props) => {
  return (
    <div>
      Hello, {props.userInfo.name}! Your age is {props.userInfo.age} and you live in {props.userInfo.city}.
    </div>
  );
};

export default Greeting;

App 组件中传递 userInfo 属性:

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

const App = () => {
  return (
    <div>
      <Greeting userInfo={{ name: 'John', age: 25, city: 'New York' }} />
    </div>
  );
};

export default App;

Greeting 组件从 userInfo 对象中提取出相应的信息并显示在页面上。

  1. 传递函数: 假设 Greeting 组件有一个按钮,点击按钮时需要调用父组件传递过来的函数。修改 Greeting 组件如下:
import React from'react';

const Greeting = (props) => {
  return (
    <div>
      Hello, {props.name}!
      <button onClick={props.onButtonClick}>Click me</button>
    </div>
  );
};

export default Greeting;

App 组件中传递 onButtonClick 函数:

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

const App = () => {
  const handleButtonClick = () => {
    console.log('Button clicked!');
  };

  return (
    <div>
      <Greeting name="John" onButtonClick={handleButtonClick} />
    </div>
  );
};

export default App;

当用户点击按钮时,handleButtonClick 函数会被调用,并在控制台输出 Button clicked!

默认 Props

有时候,我们希望子组件在没有接收到某些 Props 时,有一个默认的值。可以通过 defaultProps 来实现。

修改 Greeting 组件如下:

import React from'react';

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

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

export default Greeting;

现在,如果在 App 组件中没有传递 name 属性,Greeting 组件会使用默认值 Guest

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

const App = () => {
  return (
    <div>
      <Greeting />
    </div>
  );
};

export default App;

页面将显示 Hello, Guest!

Props 校验

为了确保传递给子组件的 Props 符合预期的类型和格式,我们可以使用 prop-types 库进行 Props 校验。

首先安装 prop-types

npm install prop-types

然后修改 Greeting 组件如下:

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

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

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

export default Greeting;

在上述代码中,propTypes 对象定义了 name 属性必须是字符串类型,并且是必填的。如果在 App 组件中传递的 name 不是字符串类型,或者没有传递 name 属性,React 开发环境会在控制台给出警告信息。

React State 基础入门

什么是 State

State 是 React 组件中用于存储和管理可变数据的一种机制。与 Props 不同,State 是组件内部私有的,可以在组件内部进行修改。State 的变化会触发组件的重新渲染,从而更新 UI。

State 通常用于表示组件的动态数据,例如用户输入、组件的展开/折叠状态等。

使用 State

在 React 中,使用 useState 钩子来在函数组件中添加 State。useState 是 React 提供的一种在函数组件中使用状态的方式。

下面是一个简单的计数器示例,展示如何使用 useState

import React, { useState } from'react';

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

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

  const decrement = () => {
    setCount(count - 1);
  };

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

export default Counter;

在上述代码中,通过 useState(0) 初始化了一个名为 count 的状态,初始值为 0useState 返回一个数组,第一个元素是当前状态值(count),第二个元素是一个用于更新状态的函数(setCount)。

increment 函数通过调用 setCount(count + 1)count 的值加 1,decrement 函数通过调用 setCount(count - 1)count 的值减 1。每次状态更新时,组件会重新渲染,从而在页面上显示最新的 count 值。

State 的更新机制

  1. 批量更新: React 会批量处理 State 的更新,以提高性能。例如,在一个事件处理函数中多次调用 setState,React 会将这些更新合并成一次更新,而不是每次调用 setState 都立即重新渲染组件。
import React, { useState } from'react';

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

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};

export default Example;

在上述代码中,虽然在 handleClick 函数中调用了三次 setCount,但实际上组件只会重新渲染一次,最终 count 的值为 3

  1. 不可变数据更新: 在更新 State 时,需要保持数据的不可变性。不要直接修改 State 对象,而是创建一个新的对象来更新。

例如,假设有一个包含对象的 State:

import React, { useState } from'react';

const ObjectStateExample = () => {
  const [user, setUser] = useState({ name: 'John', age: 25 });

  const updateUserAge = () => {
    // 错误做法:直接修改对象
    // user.age = 26;
    // setUser(user);

    // 正确做法:创建新对象
    const newUser = {...user, age: 26 };
    setUser(newUser);
  };

  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={updateUserAge}>Update Age</button>
    </div>
  );
};

export default ObjectStateExample;

updateUserAge 函数中,使用展开运算符 ... 创建了一个新的 user 对象,并更新了 age 属性,然后通过 setUser 更新 State。这样做可以确保 React 能够正确检测到 State 的变化并进行重新渲染。

State 与 Props 的结合使用

在实际应用中,State 和 Props 经常结合使用。父组件可以通过 Props 将 State 传递给子组件,子组件可以通过调用父组件传递过来的函数来更新父组件的 State。

假设有一个 Parent 组件和一个 Child 组件,Parent 组件有一个 count 状态,Child 组件有一个按钮,点击按钮可以增加 Parent 组件的 count 值。

首先创建 Child 组件:

import React from'react';

const Child = (props) => {
  return (
    <div>
      <button onClick={props.increment}>Increment in Parent</button>
    </div>
  );
};

export default Child;

然后创建 Parent 组件:

import React, { useState } from'react';
import Child from './Child';

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

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

  return (
    <div>
      <p>Count in Parent: {count}</p>
      <Child increment={increment} />
    </div>
  );
};

export default Parent;

在上述代码中,Parent 组件将 increment 函数通过 Props 传递给 Child 组件。当用户点击 Child 组件中的按钮时,会调用 increment 函数,从而更新 Parent 组件的 count 状态,并在页面上显示更新后的 count 值。

复杂 State 管理

随着应用程序的增长,组件的 State 可能变得复杂。对于复杂的 State 管理,有几种常见的解决方案:

  1. Lifting State Up: 将共享的 State 提升到最近的共同父组件中,让父组件通过 Props 将 State 传递给需要的子组件,并通过传递函数让子组件能够更新 State。例如,假设有两个兄弟组件 ComponentAComponentB 需要共享某个状态,将这个状态提升到它们的父组件 Parent 中进行管理。

  2. Context: React 的 Context API 可以让组件在不通过层层传递 Props 的情况下共享数据。它适用于一些全局的数据,例如当前用户信息、主题设置等。

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

const UserContext = createContext();

const UserProvider = ({ children }) => {
  const [user, setUser] = useState({ name: 'John', age: 25 });

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

export { UserContext, UserProvider };

然后在需要使用 user 状态的组件中:

import React from'react';
import { UserContext } from './UserContext';

const SomeComponent = () => {
  const { user, setUser } = React.useContext(UserContext);

  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={() => setUser({...user, age: user.age + 1 })}>Increment Age</button>
    </div>
  );
};

export default SomeComponent;

这样,SomeComponent 组件可以直接从 UserContext 中获取 user 状态和 setUser 函数,而不需要通过层层传递 Props。

  1. 状态管理库: 对于大型应用程序,还可以使用状态管理库,如 Redux 或 MobX。Redux 使用单一的状态树来管理整个应用程序的状态,通过 actions 和 reducers 来更新状态。MobX 则采用响应式编程的方式来管理状态,通过 observable 和 observer 来实现状态的自动更新。

以 Redux 为例,首先安装 reduxreact - redux

npm install redux react-redux

然后创建一个 reducer:

const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

接着创建一个 store:

import { createStore } from'redux';
import counterReducer from './counterReducer';

const store = createStore(counterReducer);

export default store;

在 React 组件中使用 Redux:

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

const Counter = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

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

export default Counter;

在上述代码中,useSelector 用于从 Redux store 中获取状态,useDispatch 用于获取 dispatch 函数,通过 dispatch 函数来触发 actions 从而更新 store 中的状态。

通过深入理解 React 的 Props 和 State,开发者可以更好地构建动态、交互式的前端应用程序,处理各种复杂的业务逻辑和用户交互场景。无论是小型项目还是大型应用,正确运用 Props 和 State 是实现高效、可维护的 React 应用的关键。