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

创建可复用的React组件

2021-08-044.8k 阅读

理解 React 组件

在 React 开发中,组件是构建用户界面的基本单元。一个 React 组件本质上是一个 JavaScript 函数或类,它接收输入(props)并返回一个描述用户界面的 React 元素。例如,一个简单的展示用户名的组件可以这样写:

import React from 'react';

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

export default UserName;

在上述代码中,UserName 是一个函数式组件,它接收 props 对象作为参数,props 中包含了我们传入的 name 属性。然后通过返回一个包含用户名的 div 元素来渲染 UI。

组件的类型

  1. 函数式组件:正如上面的 UserName 组件所示,函数式组件是一个简单的 JavaScript 函数,它接收 props 并返回 React 元素。它们没有自己的状态(state),通常用于展示性的 UI 部分,代码简洁且易于理解。例如:
const Button = (props) => {
  return <button onClick={props.onClick}>{props.label}</button>;
};

这个 Button 组件接收 onClick 点击事件处理函数和 label 按钮文本作为 props,并渲染一个按钮。

  1. 类组件:类组件是使用 ES6 类语法定义的 React 组件。它们可以拥有自己的状态(state)和生命周期方法。例如:
import React, { Component } from'react';

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

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

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

export default Counter;

在这个 Counter 类组件中,我们在 constructor 中初始化了 state,包含一个 count 变量。increment 方法用于更新 staterender 方法返回了渲染的 UI。

组件的复用原则

  1. 单一职责原则:每个组件应该只负责一项特定的功能。例如,一个 Navigation 组件应该只负责导航栏的渲染和交互,而不应该同时处理用户登录逻辑等无关功能。假设我们有一个 Navigation 组件:
const Navigation = () => {
  return (
    <nav>
      <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">About</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </nav>
  );
};

这样的 Navigation 组件专注于导航栏的结构展示,遵循单一职责原则,易于复用。

  1. 高内聚,低耦合:组件内部的代码应该紧密相关(高内聚),而组件之间的依赖应该尽量少(低耦合)。以一个电商项目为例,ProductCard 组件展示商品信息,它应该只依赖于商品数据(props),而不应该依赖于购物车组件的内部逻辑。
const ProductCard = (props) => {
  return (
    <div className="product-card">
      <img src={props.image} alt={props.title} />
      <h3>{props.title}</h3>
      <p>{props.price}</p>
    </div>
  );
};

ProductCard 组件通过 props 接收商品图片、标题和价格等信息,与其他组件的耦合度低。

创建可复用的函数式组件

  1. 基础组件的创建:以创建一个通用的 Input 组件为例,它可以接收不同的类型(如 textpassword 等)和占位符文本。
import React from'react';

const Input = (props) => {
  return <input type={props.type} placeholder={props.placeholder} />;
};

export default Input;

在其他组件中使用这个 Input 组件就非常方便,比如:

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

const LoginForm = () => {
  return (
    <form>
      <Input type="text" placeholder="Username" />
      <Input type="password" placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  );
};

export default LoginForm;
  1. 添加交互逻辑:有时候我们希望 Input 组件能实时获取用户输入的值。我们可以通过 onChange 事件和 props 中的回调函数来实现。
import React from'react';

const Input = (props) => {
  const handleChange = (e) => {
    props.onChange(e.target.value);
  };

  return <input type={props.type} placeholder={props.placeholder} onChange={handleChange} />;
};

export default Input;

在使用 Input 组件的地方:

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

const SignupForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleEmailChange = (value) => {
    setEmail(value);
  };

  const handlePasswordChange = (value) => {
    setPassword(value);
  };

  return (
    <form>
      <Input type="email" placeholder="Email" onChange={handleEmailChange} />
      <Input type="password" placeholder="Password" onChange={handlePasswordChange} />
      <button type="submit">Signup</button>
    </form>
  );
};

export default SignupForm;

创建可复用的类组件

  1. 状态管理与复用:以一个 Toggle 开关组件为例,它有两种状态:开和关。
import React, { Component } from'react';

class Toggle extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isOn: false
    };
  }

  toggleState = () => {
    this.setState((prevState) => ({
      isOn:!prevState.isOn
    }));
  };

  render() {
    return (
      <button onClick={this.toggleState}>
        {this.state.isOn? 'On' : 'Off'}
      </button>
    );
  }
}

export default Toggle;

在其他组件中可以复用这个 Toggle 组件,比如在一个设置页面:

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

const SettingsPage = () => {
  return (
    <div>
      <h2>Settings</h2>
      <Toggle />
    </div>
  );
};

export default SettingsPage;
  1. 生命周期方法的应用:类组件拥有生命周期方法,如 componentDidMountcomponentDidUpdatecomponentWillUnmount。假设我们有一个 Timer 组件,在组件挂载时开始计时,在组件更新时记录时间变化,在组件卸载时清除定时器。
import React, { Component } from'react';

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

  componentDidMount() {
    this.timer = setInterval(() => {
      this.setState((prevState) => ({
        time: prevState.time + 1
      }));
    }, 1000);
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.time!== this.state.time) {
      console.log(`Time updated: ${this.state.time}`);
    }
  }

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

  render() {
    return (
      <div>
        <p>Time elapsed: {this.state.time} seconds</p>
      </div>
    );
  }
}

export default Timer;

使用 props 定制组件

  1. 传递基本数据类型:我们可以向组件传递字符串、数字等基本数据类型作为 props。比如在一个 Title 组件中:
const Title = (props) => {
  return <h1>{props.text}</h1>;
};

// 使用 Title 组件
const Page = () => {
  return (
    <div>
      <Title text="Welcome to My Page" />
    </div>
  );
};
  1. 传递函数作为 props:传递函数作为 props 可以让父组件控制子组件的行为。以一个 Button 组件为例,父组件传递点击处理函数。
const Button = (props) => {
  return <button onClick={props.onClick}>{props.label}</button>;
};

// 在父组件中使用
const App = () => {
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <div>
      <Button label="Click Me" onClick={handleClick} />
    </div>
  );
};
  1. 默认 props:为了防止 props 未传递导致组件出错,我们可以设置默认 props。比如在 Input 组件中设置默认的 typetext
const Input = (props) => {
  return <input type={props.type} placeholder={props.placeholder} />;
};

Input.defaultProps = {
  type: 'text'
};

这样,即使在使用 Input 组件时没有传递 type 属性,它也会默认使用 text 类型。

组件的组合与嵌套

  1. 简单的组件组合:我们可以将多个组件组合在一起形成更复杂的 UI。例如,将 InputButton 组件组合成一个 SearchBar 组件。
import React from'react';
import Input from './Input';
import Button from './Button';

const SearchBar = () => {
  return (
    <div>
      <Input type="text" placeholder="Search..." />
      <Button label="Search" />
    </div>
  );
};

export default SearchBar;
  1. 嵌套组件:有时候组件内部需要嵌套其他组件。比如在一个 List 组件中嵌套 ListItem 组件。
import React from'react';

const ListItem = (props) => {
  return <li>{props.text}</li>;
};

const List = (props) => {
  return (
    <ul>
      {props.items.map((item, index) => (
        <ListItem key={index} text={item} />
      ))}
    </ul>
  );
};

// 使用 List 组件
const App = () => {
  const items = ['Apple', 'Banana', 'Cherry'];
  return (
    <div>
      <List items={items} />
    </div>
  );
};
  1. 通过 props 传递子组件:React 允许通过 props.children 来传递子组件。例如,一个 Panel 组件可以接收任意子组件并展示在面板内。
const Panel = (props) => {
  return (
    <div className="panel">
      {props.children}
    </div>
  );
};

// 使用 Panel 组件
const Content = () => {
  return (
    <Panel>
      <h2>Panel Title</h2>
      <p>This is the content inside the panel.</p>
    </Panel>
  );
};

处理组件的样式

  1. 内联样式:在 React 中可以使用内联样式来为组件添加样式。例如,为 Button 组件添加内联样式。
const Button = (props) => {
  const style = {
    backgroundColor: 'blue',
    color: 'white',
    padding: '10px 20px',
    border: 'none',
    borderRadius: '5px'
  };

  return <button style={style}>{props.label}</button>;
};
  1. CSS 模块:CSS 模块可以让我们为每个组件创建独立的 CSS 文件,避免样式冲突。首先,创建一个 Button.module.css 文件:
.button {
  background-color: green;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
}

然后在 Button 组件中引入:

import React from'react';
import styles from './Button.module.css';

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

export default Button;
  1. Styled Components:Styled Components 是一个流行的库,它允许我们用 JavaScript 编写样式。首先安装 styled - componentsnpm install styled - components。然后创建一个 Button 组件:
import React from'react';
import styled from'styled - components';

const StyledButton = styled.button`
  background-color: orange;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
`;

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

export default Button;

优化可复用组件

  1. 性能优化:对于频繁渲染的组件,可以使用 React.memo 来优化函数式组件的性能。例如,ListItem 组件:
const ListItem = React.memo((props) => {
  return <li>{props.text}</li>;
});

React.memo 会对 props 进行浅比较,如果 props 没有变化,组件将不会重新渲染。对于类组件,可以通过 shouldComponentUpdate 方法来控制组件是否更新。

class MyComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.value!== nextProps.value) {
      return true;
    }
    return false;
  }

  render() {
    return <div>{this.props.value}</div>;
  }
}
  1. 代码拆分:随着项目的增长,可复用组件的代码可能变得庞大。我们可以使用代码拆分来将组件代码拆分成更小的部分。例如,使用动态 import() 来按需加载组件。假设我们有一个大型的 Dashboard 组件,它包含一些不常用的子组件:
const Dashboard = () => {
  const [isAdvancedSectionVisible, setIsAdvancedSectionVisible] = useState(false);

  const loadAdvancedSection = React.lazy(() => import('./AdvancedSection'));

  return (
    <div>
      <h1>Dashboard</h1>
      <button onClick={() => setIsAdvancedSectionVisible(!isAdvancedSectionVisible)}>
        {isAdvancedSectionVisible? 'Hide Advanced' : 'Show Advanced'}
      </button>
      {isAdvancedSectionVisible && (
        <React.Suspense fallback={<div>Loading...</div>}>
          <loadAdvancedSection />
        </React.Suspense>
      )}
    </div>
  );
};

这样,只有在用户点击按钮显示高级部分时,AdvancedSection 组件的代码才会被加载。

  1. 文档化组件:为了让其他开发者更好地复用组件,需要对组件进行文档化。可以使用工具如 Storybook 来创建组件的文档和示例。首先安装 Storybooknpx sb init。然后在 src/stories 目录下创建一个 Button.stories.js 文件:
import React from'react';
import Button from './Button';
import { storiesOf } from '@storybook/react';

storiesOf('Button', module)
 .add('default', () => <Button label="Default Button" />)
 .add('with custom color', () => (
      <Button label="Custom Color Button" style={{ backgroundColor:'red' }} />
    ));

通过 Storybook,我们可以直观地展示组件的不同状态和用法,方便团队成员复用。

通过以上详细的讲解和示例,希望你对创建可复用的 React 组件有了更深入的理解,能够在实际项目中构建出高效、可维护且易于复用的组件。