Skip to content

React 核心概念

本文介绍React的核心概念,包括组件、Props、State、生命周期、事件处理等基础知识点。

什么是React?

React是一个用于构建用户界面的JavaScript库,由Facebook开发并维护。它主要用于构建单页应用程序(SPA),但也可以用于开发移动应用和其他平台。

React的特点

  • 声明式编程:React使用声明式范式,让你可以轻松描述应用的最终状态,而不必关心状态转换的具体过程。
  • 基于组件:构建封装的、可管理自身状态的组件,然后组合它们以构建复杂的UI。
  • 虚拟DOM:React维护一个内存中的虚拟DOM,通过比较(diffing)算法高效更新实际DOM。
  • 单向数据流:数据从父组件流向子组件,使应用中的数据流动可预测。
  • JSX语法:JavaScript的语法扩展,允许在JavaScript中编写类似HTML的代码。

组件

组件是React应用的核心构建块,它们是可重用的、独立的代码片段,返回要在页面上渲染的React元素。

函数组件

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 使用箭头函数
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
};

// 简化版本
const Welcome = props => <h1>Hello, {props.name}</h1>;

类组件

jsx
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

组件组合

jsx
function App() {
  return (
    <div>
      <Welcome name="Alice" />
      <Welcome name="Bob" />
      <Welcome name="Charlie" />
    </div>
  );
}

JSX

JSX是JavaScript的语法扩展,它看起来像HTML,但具有JavaScript的全部功能。

jsx
const element = <h1>Hello, world!</h1>;

// 使用JavaScript表达式
const name = "John";
const element = <h1>Hello, {name}!</h1>;

// 属性
const element = <img src={user.avatarUrl} alt="User avatar" />;

// 子元素
const element = (
  <div>
    <h1>Title</h1>
    <p>Paragraph</p>
  </div>
);

JSX防注入攻击

React DOM在渲染所有输入内容之前,默认会进行转义,有助于防止XSS(跨站脚本)攻击。

jsx
const title = "<script>alert('XSS');</script>";
// 这是安全的,脚本不会执行
const element = <h1>{title}</h1>;

JSX表示对象

Babel会将JSX编译为React.createElement()调用。

jsx
// 这两个例子是等价的
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

Props

Props(属性)是从父组件传递到子组件的只读数据。

jsx
// 父组件传递props
function App() {
  return <Welcome name="Sara" age={25} isAdmin={true} />;
}

// 子组件接收props
function Welcome(props) {
  return (
    <div>
      <h1>Hello, {props.name}</h1>
      <p>Age: {props.age}</p>
      {props.isAdmin && <p>Admin user</p>}
    </div>
  );
}

Props的特点

  • Props是只读的,组件不应修改自己的props
  • 所有React组件必须像纯函数一样保护它们的props不被修改

默认Props

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

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

// 类组件中
class Welcome extends React.Component {
  static defaultProps = {
    name: 'Guest'
  };
  
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Props类型检查

使用PropTypes库进行类型检查:

jsx
import PropTypes from 'prop-types';

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Welcome.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  isAdmin: PropTypes.bool
};

State

State是组件内部管理的可变数据,当state改变时,组件会重新渲染。

类组件中的State

jsx
class Counter extends React.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>
    );
  }
}

函数组件中的State (使用Hooks)

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

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

State更新可能是异步的

jsx
// 错误方式:可能导致更新丢失
this.setState({
  count: this.state.count + 1
});

// 正确方式:使用函数形式
this.setState((prevState) => ({
  count: prevState.count + 1
}));

State合并更新

jsx
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      email: ''
    };
  }
  
  handleNameChange = (e) => {
    // 只更新name,email保持不变
    this.setState({ name: e.target.value });
  }
  
  render() {
    return (
      <form>
        <input 
          type="text" 
          value={this.state.name} 
          onChange={this.handleNameChange} 
        />
      </form>
    );
  }
}

生命周期

类组件有多个生命周期方法,让你在组件的不同阶段执行代码。

挂载阶段

jsx
class LifecycleDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { data: null };
    console.log('1. Constructor');
  }
  
  static getDerivedStateFromProps(props, state) {
    console.log('2. getDerivedStateFromProps');
    return null; // 返回null表示不更新state
  }
  
  componentDidMount() {
    console.log('4. componentDidMount');
    // 适合进行API调用、订阅事件等
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => this.setState({ data }));
  }
  
  render() {
    console.log('3. render');
    return <div>Lifecycle Demo</div>;
  }
}

更新阶段

jsx
class LifecycleDemo extends React.Component {
  // ... 挂载阶段方法 ...
  
  shouldComponentUpdate(nextProps, nextState) {
    console.log('5. shouldComponentUpdate');
    // 返回true允许更新,返回false阻止更新
    return true;
  }
  
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('7. getSnapshotBeforeUpdate');
    // 在DOM更新之前捕获一些信息(如滚动位置)
    return null;
  }
  
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('8. componentDidUpdate');
    // 组件更新后执行,可以进行DOM操作或网络请求
  }
  
  render() {
    console.log('6. render');
    return <div>Lifecycle Demo</div>;
  }
}

卸载阶段

jsx
class LifecycleDemo extends React.Component {
  // ... 其他生命周期方法 ...
  
  componentWillUnmount() {
    console.log('9. componentWillUnmount');
    // 清理工作:取消网络请求、清除定时器、取消订阅等
  }
}

错误处理

jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError(error) {
    // 更新state使下一次渲染显示降级UI
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    // 记录错误信息
    console.error('Error:', error);
    console.error('Error Info:', errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    
    return this.props.children;
  }
}

// 使用错误边界
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

事件处理

React事件使用驼峰命名法,并传递函数作为事件处理器。

jsx
// 函数组件中的事件处理
function Button() {
  const handleClick = (e) => {
    e.preventDefault(); // 阻止默认行为
    console.log('Button clicked');
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

// 类组件中的事件处理
class Button extends React.Component {
  handleClick = (e) => {
    e.preventDefault();
    console.log('Button clicked');
  }
  
  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

绑定this

在类组件中,需要确保事件处理器中的this正确绑定。

jsx
class Button extends React.Component {
  constructor(props) {
    super(props);
    // 方法1:在构造函数中绑定
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    console.log('this is:', this);
  }
  
  // 方法2:使用箭头函数类属性(需要Babel支持)
  handleClickArrow = () => {
    console.log('this is:', this);
  }
  
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click me (bound in constructor)</button>
        <button onClick={this.handleClickArrow}>Click me (arrow function)</button>
        {/* 方法3:在回调中使用箭头函数(每次渲染都会创建新函数,可能影响性能) */}
        <button onClick={() => this.handleClick()}>Click me (inline arrow)</button>
      </div>
    );
  }
}

向事件处理器传递参数

jsx
// 方法1:使用箭头函数
<button onClick={(e) => this.handleClick(id, e)}>Delete</button>

// 方法2:使用bind(事件对象e会被自动作为最后一个参数传入)
<button onClick={this.handleClick.bind(this, id)}>Delete</button>

条件渲染

根据应用状态有条件地渲染UI部分。

jsx
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  
  // 方法1:使用if语句
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

// 方法2:使用三元运算符
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  return isLoggedIn ? <UserGreeting /> : <GuestGreeting />;
}

// 方法3:使用逻辑与运算符(短路求值)
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>You have {unreadMessages.length} unread messages.</h2>
      }
    </div>
  );
}

阻止组件渲染

jsx
function WarningBanner(props) {
  if (!props.warn) {
    return null; // 不渲染任何内容
  }
  
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

列表渲染

使用JavaScript的map()方法渲染多个组件。

jsx
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  
  return <ul>{listItems}</ul>;
}

// 内联map
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      )}
    </ul>
  );
}

键(Keys)

键帮助React识别哪些项目已更改、添加或删除。

jsx
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

// 如果没有稳定ID,可以使用索引作为键(不推荐)
const todoItems = todos.map((todo, index) =>
  <li key={index}>
    {todo.text}
  </li>
);

表单

在React中,表单元素通常由React组件控制。

受控组件

jsx
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
  }
  
  handleChange = (event) => {
    this.setState({value: event.target.value});
  }
  
  handleSubmit = (event) => {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input 
            type="text" 
            value={this.state.value} 
            onChange={this.handleChange} 
          />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

处理多个输入

jsx
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };
  }
  
  handleInputChange = (event) => {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    
    this.setState({
      [name]: value // 使用ES6计算属性名语法
    });
  }
  
  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange}
          />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange}
          />
        </label>
      </form>
    );
  }
}

非受控组件

使用ref获取表单值,而不是通过state控制。

jsx
class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.fileInput = React.createRef();
  }
  
  handleSubmit = (event) => {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`
    );
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

组合 vs 继承

React推荐使用组合而非继承来复用组件之间的代码。

包含关系

jsx
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">Welcome</h1>
      <p className="Dialog-message">Thank you for visiting our spacecraft!</p>
    </FancyBorder>
  );
}

特殊情况

jsx
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={<Contacts />}
      right={<Chat />}
    />
  );
}

Context

Context提供了一种在组件树中共享值的方式,而不必显式地通过每个层级传递props。

jsx
// 创建Context
const ThemeContext = React.createContext('light');

// 提供Context
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 中间组件不需要传递theme prop
function Toolbar() {
  return <ThemedButton />;
}

// 消费Context(类组件)
class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  
  render() {
    return <Button theme={this.context} />;
  }
}

// 消费Context(函数组件)
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {value => <Button theme={value} />}
    </ThemeContext.Consumer>
  );
}

// 使用useContext Hook
function ThemedButton() {
  const theme = React.useContext(ThemeContext);
  return <Button theme={theme} />;
}

代码分割

代码分割帮助你"懒加载"当前用户所需的内容,提高应用性能。

jsx
// 使用React.lazy和Suspense
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

// 基于路由的代码分割
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </Suspense>
    </Router>
  );
}

错误边界

错误边界是React组件,它可以捕获子组件树中的JavaScript错误,记录错误并显示备用UI。

jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新state使下一次渲染显示降级UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级UI并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

// 使用方式
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Refs

Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。

jsx
// 创建Refs(类组件)
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  
  componentDidMount() {
    // 访问DOM节点
    this.myRef.current.focus();
  }
  
  render() {
    return <input ref={this.myRef} />;
  }
}

// 使用useRef Hook(函数组件)
function MyComponent() {
  const myRef = React.useRef(null);
  
  React.useEffect(() => {
    // 访问DOM节点
    myRef.current.focus();
  }, []);
  
  return <input ref={myRef} />;
}

// 回调Refs
function CustomTextInput(props) {
  let textInput = null;
  
  function handleClick() {
    textInput.focus();
  }
  
  return (
    <div>
      <input
        type="text"
        ref={(element) => { textInput = element; }}
      />
      <button onClick={handleClick}>Focus the text input</button>
    </div>
  );
}

高阶组件 (HOC)

高阶组件是一个函数,它接受一个组件并返回一个新组件。

jsx
// 高阶组件示例
function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: selectData(DataSource, props)
      };
    }
    
    componentDidMount() {
      DataSource.addChangeListener(this.handleChange);
    }
    
    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }
    
    handleChange = () => {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }
    
    render() {
      // 传递props和注入的数据
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

// 使用HOC
const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

Render Props

Render Props是一种在React组件之间共享代码的技术,它使用一个值为函数的prop来传递需要动态渲染的内容。

jsx
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }
  
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  
  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {/* 调用render prop,传入state作为参数 */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

// 使用render prop
function App() {
  return (
    <Mouse render={mouse => (
      <p>The mouse position is {mouse.x}, {mouse.y}</p>
    )} />
  );
}

// 使用children作为函数
function App() {
  return (
    <Mouse>
      {mouse => (
        <p>The mouse position is {mouse.x}, {mouse.y}</p>
      )}
    </Mouse>
  );
}

Portals

Portals提供了一种将子节点渲染到存在于父组件以外的DOM节点的方式。

jsx
import ReactDOM from 'react-dom';

function Modal(props) {
  return ReactDOM.createPortal(
    props.children,
    document.getElementById('modal-root')
  );
}

function App() {
  return (
    <div>
      <h1>App</h1>
      <Modal>
        <h2>Modal Content</h2>
        <p>This is rendered outside the parent component's DOM hierarchy.</p>
      </Modal>
    </div>
  );
}

Fragments

Fragments允许你将子元素列表添加到一个分组中,而无需向DOM添加额外节点。

jsx
function Table() {
  return (
    <table>
      <tbody>
        <tr>
          <Columns />
        </tr>
      </tbody>
    </table>
  );
}

function Columns() {
  return (
    <React.Fragment>
      <td>Hello</td>
      <td>World</td>
    </React.Fragment>
  );
}

// 短语法
function Columns() {
  return (
    <>
      <td>Hello</td>
      <td>World</td>
    </>
  );
}

严格模式

StrictMode是一个用来突出显示应用程序中潜在问题的工具。

jsx
import React from 'react';

function App() {
  return (
    <React.StrictMode>
      <div>
        <ComponentOne />
        <ComponentTwo />
      </div>
    </React.StrictMode>
  );
}

严格模式检查:

  1. 识别不安全的生命周期方法
  2. 关于使用过时字符串ref API的警告
  3. 关于使用废弃的findDOMNode方法的警告
  4. 检测意外的副作用
  5. 检测过时的context API