发布于 2026-01-06 2 阅读
0

我为什么要关注 React?

我为什么要关注 React?

假设你已经掌握了如何使用原生 JavaScript 或 jQuery 编写类似应用程序的功能。你可以获取数据、发送数据以及操作 DOM,而无需任何框架。既然你已经能够不用 React 就完成你想做的事情,为什么还要学习 React 呢?

React是什么

React 是一个 JavaScript 库,它简化了单页应用程序用户界面的构建。您可以构建组件,这些组件最终会像您自己的自定义 HTML 标签一样在 JavaScript 中运行。

你可以直接在 React 代码中编写类似HTML 的内容,这叫做JSX。你可以在 JSX 中包含 JavaScript 表达式,这样就可以实现一些很棒的功能,例如在渲染的组件发生变化时输出数据。

您可以将大量实际的 HTML 元素塞进一个组件中,每次使用该组件时,浏览器都会渲染其中包含的所有元素。您的组件还可以捆绑各种行为,甚至样式,以便在每次重用该组件时都能应用这些行为和样式。

你的组件可以拥有状态。状态是一组变量的集合,用于存储对该组件至关重要的数据。如果组件的状态发生变化,你可以更新渲染后的组件以反映这种变化。

每个组件都有生命周期。你可以在组件预定义的生命周期方法中编写自定义代码。当该方法描述的事件发生时,React 会运行你的生命周期方法。以下是一些生命周期方法及其触发条件的示例:

  • constructor()– 组件创建后立即运行。
  • componentDidMount()– 在组件添加到 DOM 后运行。这是从数据库或外部 API 获取数据并将其添加到组件状态的绝佳位置。
  • componentDidUpdate()– 当 React 更新组件时运行。当组件状态改变或组件的某个属性获得新值时,此函数将运行。

这就是 React 的简要介绍,尽管它本身是一个非常庞大的主题。本文并非教程;我只是想提供足够的背景知识,以便向您展示它的用途。如果您想学习React,不妨从官方教程开始。

更新 DOM 中的数据真是件麻烦事🍑

更新数据之所以麻烦,并非因为用原生 JavaScript 实现起来有多难,而是因为事后很难进行逻辑分析。实际操作过程其实很简单:我只需选择一个元素,然后修改它的innerHTML属性,数据就更新好了。

const elementToChange = document.querySelector('h1');
elementToChange.innerHTML = "New Heading Value";
Enter fullscreen mode Exit fullscreen mode

现在,当我们以后需要维护这个应用时,我们需要检查所有更新过 DOM 的地方。如果页面上的数据出现错误,任何一个更新点都可能存在问题。直接通过 DOM 传递数据innerHTML也很危险,因为攻击者可能会在你的网站上运行恶意代码,窃取用户信息。

在 React 中,我们将对应用程序重要的数据维护在应用程序的状态中,并让 React 负责在数据发生变化时更新 DOM。

广泛支持的组件

想想看,一个登录表单。在我们看来,它只是一个单一的元素,但在网络上,它通常包含五个不同的元素:两个标签、两个输入框和一个按钮。

如果能在构建应用时就把所有功能都视为一个单独的组件,岂不是很棒?好消息!Web Components就能让你在无需任何框架的情况下做到这一点……前提是你只需要支持 Chrome、Firefox 和 Opera 的较新版本。

这很棒,但你很可能需要更广泛的浏览器支持。React 允许你构建与 IE 9+ 和所有现代浏览器兼容的组件,这样,下次你想在视图中添加登录表单时,就可以像这样添加:

<SignInForm />
Enter fullscreen mode Exit fullscreen mode

你需要自己构建一次这个组件(因为 React 本身并不知道你的应用中应该包含什么样的“SignInForm”),但关键在于,你可以选择构建一次,然后根据需要多次复用。它的外观和行为会随应用场景一起迁移。

想成为一名网页开发人员吗?无论您处于转型期的哪个阶段,我都能提供帮助。欢迎在RadDevon申请免费指导课程!

例如:日出时分

为了展示这两者的区别,我开发了一个简单的应用程序,可以根据你的纬度和经度显示日出日落时间。

日出时报(原生JS)

这是该应用基础版本的 JavaScript 代码。如果您想查看 HTML 和 CSS 代码,请查看下方嵌入的 Codepen 演示中的相应标签页。

function debounced(delay, fn) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn(...args);
      timerId = null;
    }, delay);
  };
}

function updateTimes(lat, long) {
  if (lat && long) {
    return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
      .then(response => {
        if (!response.ok) {
          sunriseTimeElement.innerHTML = "Invalid";
          sunsetTimeElement.innerHTML = "Invalid";
          throw Error(`${response.status} response on times request`);
        }
        return response.json();
      })
      .then(data => {
        sunriseTimeElement.innerHTML = data.results.sunrise;
        sunsetTimeElement.innerHTML = data.results.sunset;
      })
      .catch(error => {
        console.error(error.message);
      });
  }
}

function updateTimesFromInput() {
  const lat = latField.value;
  const long = longField.value;
  updateTimes(lat, long);
}

const updateTimesFromInputDebounced = debounced(500, updateTimesFromInput);

const sunriseTimeElement = document.querySelector(".sunrise .time");
const sunsetTimeElement = document.querySelector(".sunset .time");
const latField = document.querySelector("#lat");
const longField = document.querySelector("#long");

navigator.geolocation.getCurrentPosition(function(position) {
  const lat = position.coords.latitude;
  const long = position.coords.longitude;
  latField.value = lat;
  longField.value = long;
  updateTimes(lat, long);
});

[latField, longField].forEach(field => {
  const events = ["keyup", "change", "input"];
  events.forEach(event => {
    field.addEventListener(event, updateTimesFromInputDebounced);
  });
});
Enter fullscreen mode Exit fullscreen mode

请在CodePen上查看Devon Campbell ( @raddevon )的Pen Sunrise 应用(原始版本)

你会注意到这个版本有一些优点。它比 React 版本短得多。部分原因是它没有使用组件(组件在 React 版本中增加了一些开销),部分原因是状态全部都在文档中(这使得维护和分析出错原因变得困难)。

以下是一些可能有助于您理解背景信息的其他说明:

  • 我使用了数字字段,所以不需要进行验证。实际上,我仍然应该进行验证,因为并非所有浏览器都支持数字字段,但我并不担心这个示例无法直接用于生产环境。
  • 整个文档(标题、表单和输出)已经包含在 HTML 文档中。我们在 JavaScript 中所做的只是在获取新输入结果后更新输出值。
  • 由于我的代码依赖于正确选择元素,如果有人在我之后更改了元素的类名或 ID,应用程序可能会崩溃。我就是这样选择元素的。由于状态保存在那里,如果我无法访问这些元素,应用程序就无法执行所需的操作。
  • 请注意,为了确保能够捕获经纬度字段的任何变化,我不得不采用繁琐的方式绑定多个事件。数字字段会根据其变化方式发出不同的事件,因此我必须为每个数字字段创建一个绑定。(您将在 JavaScript 代码的末尾看到我的意思。)
  • 你会注意到,debounced无论是这个应用的当前版本还是 React 版本,都存在一个奇怪的功能。时间数据来自一个 API。我不想频繁调用这个 API,所以并不想每次用户按键都发起一次请求。防抖功能限制了函数的调用频率。最多,这个应用每半秒会向 API 发起一次请求。

日出时报(React)

以下是该应用 React 版本的 JavaScript 代码。如果您感兴趣,也可以在下方嵌入的 Codepen 演示中查看 HTML 和 CSS 代码。

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function debounced(delay, fn) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn(...args);
      timerId = null;
    }, delay);
  };
}

class CoordinatesForm extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <form>
        <LatField
          updateLat={newLat => {
            this.props.updateCoords({ lat: newLat });
          }}
          lat={this.props.lat}
        />
        <LongField
          updateLong={newLong => {
            this.props.updateCoords({ long: newLong });
          }}
          long={this.props.long}
        />
      </form>
    );
  }
}

class LatField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: this.props.lat
    };
  }

  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    if (lat !== prevProps.lat) {
      this.setState({ lat });
    }
  }

  updateLat = event => {
    let newLat = event.target.value;
    if (!newLat) {
      this.setState({ lat: 0 });
      return this.props.updateLat(0);
    }

    if (isNumeric(newLat)) {
      newLat = parseFloat(newLat);
    } else if (newLat !== "-") {
      return;
    }
    this.setState({ lat: newLat });
    this.props.updateLat(newLat);
  };

  render() {
    return (
      <label for="">
        lat:
        <input
          type="text"
          id="lat"
          value={this.state.lat}
          onChange={this.updateLat}
        />
      </label>
    );
  }
}

class LongField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      long: this.props.long
    };
  }

  componentDidUpdate(prevProps) {
    const long = this.props.long;
    if (long !== prevProps.long) {
      this.setState({ long });
    }
  }

  updateLong = event => {
    let newLong = event.target.value;
    if (!newLong) {
      this.setState({ long: 0 });
      return this.props.updateLong(0);
    }

    if (isNumeric(newLong)) {
      newLong = parseFloat(newLong);
    } else if (newLong !== "-") {
      return;
    }
    this.setState({ long: newLong });
    this.props.updateLong(newLong);
  };

  render() {
    return (
      <label for="">
        long:
        <input
          type="text"
          id="long"
          value={this.state.long}
          onChange={this.updateLong}
        />
      </label>
    );
  }
}

class TimesDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      sunrise: "Unknown",
      sunset: "Unknown"
    };
  }

  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    const long = this.props.long;
    if (lat !== prevProps.lat || long !== prevProps.long) {
      this.updateTimesDebounced(lat, long);
    }
  }

  updateTimes = (lat, long) => {
    if (isNumeric(lat) && isNumeric(long)) {
      return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
        .then(response => {
          if (!response.ok) {
            this.setState({
              sunrise: "Invalid",
              sunset: "Invalid"
            });
            throw Error(`${response.status} response on times request`);
          }
          return response.json();
        })
        .then(data => {
          this.setState({
            sunrise: data.results.sunrise,
            sunset: data.results.sunset
          });
        })
      .catch(error => {
        console.error(error.message);
      });
    }
  }

  updateTimesDebounced = debounced(500, this.updateTimes)

  render() {
    return (
      <div>
        <SunriseTime time={this.state.sunrise} />
        <SunsetTime time={this.state.sunset} />
      </div>
    );
  }
}

class SunriseTime extends React.Component {
  render() {
    return <div class="sunrise">Sunrise: {this.props.time}</div>;
  }
}

class SunsetTime extends React.Component {
  render() {
    return <div class="sunset">Sunset: {this.props.time}</div>;
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: 0,
      long: 0
    };
  }

  updateCoords = updateObject => {
    this.setState(updateObject);
  };

  componentDidMount() {
    navigator.geolocation.getCurrentPosition(position => {
      const lat = position.coords.latitude;
      const long = position.coords.longitude;
      this.setState({ lat, long });
    }, error => {
      console.error('Couldn\'t get your current position from the browser');
    });
  }

  render() {
    return (
      <div>
        <CoordinatesForm
          lat={this.state.lat}
          long={this.state.long}
          updateCoords={this.updateCoords}
        />
        <TimesDisplay lat={this.state.lat} long={this.state.long} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector(".app"));

Enter fullscreen mode Exit fullscreen mode

请在CodePen上查看Devon Campbell ( @raddevon )的Pen Sunrise 应用(React 版本)

我是 React 的粉丝,但我不得不承认,用 React 实现同样的功能需要编写如此多的代码,这让我非常震惊。这主要是因为我把所有功能都组件化了,而且需要在组件之间共享状态。我的意思是这样的。

有一个名为“App”的外部组件,它包含了所有其他组件,并保存着所有其他组件使用的状态。App 组件内部有两个组件:“CoordinatesForm”和“TimesDisplay”。这两个组件各自又包含两个子组件,分别对应于它们的两个值(纬度/经度和日出/日落)。例如,用户可能会更改“纬度”字段。由于组件之间很难共享数据(除非通过共同的父组件),因此该更改需要反映在 App 的状态中。

这意味着,我需要在组件树中上下传递状态。在 React 中,要设置父组件的状态,我需要在父组件上创建一个方法来设置其状态。然后,我可以通过props(React 中传递给子组件的数据)将状态传递给子组件。子组件调用该方法,并传入它们需要的数据,以便更新父组件的状态。

为了让数据向下传递,我不断地通过 props 传递数据,直到它到达需要它的组件。这并不实现,但确实比直接操作 DOM 需要编写更多的代码。

这样做的好处是,虽然我需要稍微调整一下状态,但我只需要修改状态本身,无需编写任何脆弱的 DOM 操作代码。因为我使用的是渲染组件中传递的属性,所以 React 会在这些属性发生变化时智能地更新组件。

更多背景信息:

  • 这个版本的 HTML 文档非常简洁。这是因为页面的大部分内容都是在 React 组件渲染时由 React 生成的。我只需要 HTML 中有一个元素就可以将 React 应用渲染到该元素上。
  • React 似乎无法兼容我在另一个应用中使用的数字输入框,所以我决定在这里使用带有验证功能的标准文本输入框。验证功能也增加了一些代码。或许还有办法让数字输入框更好地工作,但目前应用运行良好。

要点:你为什么应该关注

你可以看到,在数据量庞大、UI 频繁变化的大型应用中,React 提供了一种很好的方式来分离组件,让你只关注数据本身。它并非适用于所有项目,但它非常适合为项目构建结构,使其更易于维护。

如果你是一名开发者,正在构建包含多个复杂组件的 Web 应用程序,那么你应该关注 React。它能为你和其他维护代码的人省去很多麻烦。

文章来源:https://dev.to/raddevon/why-should-i-care-about-react-5641