Brief Notes on Learning React

发表于 2022-02-05 14:30 976 字 5 min read

cos avatar

cos

FE / ACG / 手工 / 深色模式强迫症 / INFP / 兴趣广泛养两只猫的老宅女 / remote

文章介绍了 React 中事件处理、列表键值(keys)以及表单控件的核心概念。事件处理需传递函数而非字符串,且必须显式调用 preventDefault;列表中的每个元素应有唯一键值以帮助 React识别变化,键值应唯一于同级元素但无需全局唯一;表单使用“受控组件”模式,通过组件状态管理输入值,实现数据的单一来源控制;通过“提升状态”(lifting state up)将共享状态集中到最近的父组件,提升代码可维护性和调试效率。

This article has been machine-translated from Chinese. The translation may contain inaccuracies or awkward phrasing. If in doubt, please refer to the original Chinese version.

Event Handling

Handling Events – React (docschina.org)

  • You need to pass a function as the event handler, rather than a string.
<button onClick={activateLasers}>
  Activate Lasers
</button>
  • You cannot prevent default behavior by returning false. You must explicitly call preventDefault.
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

When using React, you generally don’t need to use addEventListener to add listeners to already-created DOM elements. In fact, you only need to add listeners when the element is initially rendered. Declare event handling functions as methods in the class.

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    // This binding is essential to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
      this.setState(state => ({
          isToggleOn: !state.isToggleOn
      }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

Lists and Keys

Lists & Keys – React (docschina.org)

Keys

Keys help React identify which elements have changed, such as being added or removed. Therefore, you should give each element in an array a definitive identifier.

The best key for an element is a unique string that the element has within the list. Typically, we use the id from the data as the element’s key.

Keys Must Be Unique Among Siblings

Keys used among array elements should be unique among their sibling nodes. However, they don’t need to be globally unique. When we generate two different arrays, we can use the same key values.

Forms

Forms – React (docschina.org)

In React, HTML form elements work somewhat differently from other DOM elements, because form elements typically maintain some internal state.

Controlled Components

In HTML, form elements such as <input>, <textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the component’s state property and can only be updated using setState().

We can combine these two approaches to make React’s state the “single source of truth”. The React component that renders the form also controls what happens in the form during user input. A form input element whose value is controlled by React in this way is called a “controlled component.”

The textarea Tag

In HTML, the <textarea> element defines its text through its children:

<textarea>
  Hello, this is text inside a text area
</textarea>

In React, <textarea> uses a value attribute instead. This makes forms using <textarea> very similar to forms using single-line inputs:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {      value: 'Please write an essay about your favorite DOM element.'    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('Submitted essay: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Note that this.state.value is initialized in the constructor, so the text area has a default initial value.

Lifting State Up

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to the closest common ancestor component. Let’s see how this works.

Lifting State Up – React (docschina.org)

We’ll start with a component called BoilingVerdict. It accepts a celsius temperature as a prop and prints whether it is enough to boil water.

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;  }
  return <p>The water would not boil.</p>;}

Next, we’ll create a component called Calculator. It renders an <input> for entering the temperature and keeps its value in this.state.temperature.

Additionally, it renders the BoilingVerdict component based on the current input value.

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};  }

  handleChange(e) {
    this.setState({temperature: e.target.value});  }

  render() {
    const temperature = this.state.temperature;    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input          value={temperature}          onChange={this.handleChange} />        <BoilingVerdict          celsius={parseFloat(temperature)} />      </fieldset>
    );
  }
}

Adding a Second Input

Our new requirement is to provide a Fahrenheit input in addition to the existing Celsius input, and keep the two inputs synchronized.

We’ll extract a TemperatureInput component from Calculator, and add a new scale prop to it that can be either "c" or "f":

const scaleNames = {  c: 'Celsius',  f: 'Fahrenheit'};
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

We can now modify the Calculator component to render two independent temperature input components:

We’ll extract a TemperatureInput component from Calculator, and add a new scale prop to it that can be either "c" or "f":

In a React application, any mutable data should have a single corresponding “source of truth.” Usually, state is first added to the component that needs it for rendering. Then, if other components also need this state, you can lift it up to the closest common ancestor. You should rely on the top-down data flow, rather than trying to synchronize state between different components.

While lifting state requires writing more “boilerplate” code than two-way binding, the benefit is that finding and isolating bugs takes less work. Since any state “lives” in a component and only that component can change it, the surface area for bugs is greatly reduced. Additionally, you can implement custom logic to reject or transform user input.

If you enjoyed this, leave a comment~

© 2020 - 2026 cos @cosine
Powered by theme astro-koharu · Inspired by Shoka