我和 React:15 分钟回顾 5 年

2025-05-28

我和 React:15 分钟回顾 5 年

我第一次听说 React 是在一个朋友给我看他写的一个项目的时候。那是一个内容管理系统:里面有表格、表单、可视化编辑器等等。我记不太清楚了,只记得那段代码真的非常混乱,看起来像这样:

// hint: you can use this codepen to follow along:
// https://codepen.io/valeriavg-the-flexboxer/pen/WNJNMRp

const app = React.createElement(
  // tag
  'button',
  // properties
  {
    onClick:function(){alert("Hello!")}
  },
  // children
  "Click Me!"
)

ReactDOM.render(app,document.getElementById('app'))
// <div id="app"></div>
Enter fullscreen mode Exit fullscreen mode

因此,当然,我很沮丧有人会写那样的东西,而你可以这样

<button onClick="alert('Hello!')">Click me!</button>
Enter fullscreen mode Exit fullscreen mode

JSX:JS 中的 HTML

一段时间过去了,令我惊讶的是,React 已经无处不在:每个招聘广告都在提到它。

于是我又试了一次。这一次,它不再只是导入一个库那么简单——不知怎么的,它变成了一门全新的语言,叫做jsx。然而,它却又让人感觉似曾相识:

const app = 
     <button onClick={()=>alert('Hello, JSX!')}> 
      Click me! 
     </button>

ReactDOM.render(app,document.getElementById('app'))
Enter fullscreen mode Exit fullscreen mode

这几乎与我的老朋友HTML相同,只是JSX允许将 HTML 页面拆分为微小的可重用动态构建块:

const One = () => <div> One </div>;
const Two = () => <div> Two </div>;
const Three = () => <div> Three </div>;
const app = (
  <div>
    <One />
    <Two />
    <Three />
  </div>
);

ReactDOM.render(app, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

然而,在幕后,它仍然是相同的代码:

const One = () => React.createElement('div',{},'One');
const Two = () => React.createElement('div',{},'Two');
const Three = () => React.createElement('div',{},'Three');
const app = React.createElement('div',{},One(),Two(),Three());
ReactDOM.render(app,document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

但是 JSX 带来了很大的不同,并且 React 终于开始对我有意义了。

有状态和无状态组件

我学到的第一件事就是“有状态”组件:

class App extends React.Component {
  constructor() {
    super()
    this.state = {
      name: ''
    }
  }

  render() {
    return (
      <div>
        <h1>Hello, {this.state.name} </h1>
        <input
          type="text"
          value={this.state.name}
          onChange={(e) => this.setState({name: e.target.value})}
        />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

或者更确切地说是第二件事,因为显然我已经熟悉了它的“无国籍”对应物。

状态组件具有在更改时触发重新渲染的状态,而无状态组件仅具有渲染部分,并且只要props相同,就会渲染完全相同的内容:

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

ReactDOM.render(<App name="React"/>, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

当时我在一家初创公司工作,该公司允许创作者向粉丝分发视频内容等。这意味着我们有一个面向创作者的仪表盘、一个面向最终用户的网站和一个应用程序。React 在仪表盘方面表现得非常出色,尤其是在函数式组件出现之后:

const App = () => {
  const [name, setName] = React.useState("");
  return (
    <div>
      <h1>Hello, {name} </h1>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

于是,项目规模不断扩大,依赖项也随之增多。我们使用了情感化组件、React-Router以及其他一些组件来管理状态。

状态管理

我尝试过的第一个状态管理库是RxJS。当然,它为项目增添了更多魔力,但是,在两个组件之间共享状态也很酷,对吧?

错了,一片混乱!我根本分不清是哪个改变了状态,这让调试过程非常费脑力,因为有时状态在实际传播console.log一微秒就被打印出来了。

从这个意义上来说, Redux对我来说要好一些,但是对于我喜欢的模块化架构来说,拥有一个巨大的存储并不方便。

因此我坚持使用 React 自己的,context因为我可以轻松拆分状态并更轻松地跟踪更新:

const NameContext = React.createContext("");

const Name = () => {
  const name = React.useContext(NameContext);
  if (!name) return "";
  return <h1> Hello, {name}!</h1>;
};

const App = () => {
  const [name, setName] = React.useState("");

  return (
    <NameContext.Provider value={name}>
      <div>
        <input
          value={name}
          onChange={(e) => setName(e.target.value)}
          placeholder="What's your name?"
        />
        <Name />
      </div>
    </NameContext.Provider>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

从代码中可以看出,这正是函数组件出现的时候。

功能组件

简而言之,函数式组件是一种尝试,通过让所有组件都成为函数并使用钩子,轻松地将有状态组件转变为无状态组件,反之亦然:

const App = () => {
  const [name, setName] = React.useState("");
  return (
    <div>
      <h1>Hello, {name} </h1>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

代码的编写和阅读确实变得更容易了,这点毋庸置疑,尽管我对hooks还是有所顾虑。首先,状态仍然需要存储在某个地方,而且最初提议围绕 hooks 构建this,但这不适用于箭头函数,而且需要依赖于 JSX 的编译(目前情况并非如此,因为它使用的是调度程序)。其次,它需要用 React 进行思考

虽然类名有点拗口,但它们其实很简单,有明确的propsand state,并且当 state 或 props 发生变化时,render就会触发相应的方法。此外,还有一些方法可以用来控制这种流程,例如shouldComponentUpdateor componentDidMount

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      name: ""
    };
  }

  componentDidMount() {
    console.log("Component did mount!");
  }

  shouldComponentUpdate(props, state) {
    console.log({
      new: { props, state },
      old: { props: this.props, state: this.state }
    });
    return true;
  }

  render() {
    return (
      <div>
        <h1>Hello, {this.state.name} </h1>
        <input
          type="text"
          value={this.state.name}
          onChange={(e) => this.setState({ name: e.target.value })}
        />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

当它变成一个带有钩子的简洁函数组件时,看起来就像魔术一样:

const App = () => {
  const [name, setName] = React.useState("");
  React.useEffect(() => {
    console.log("Mounted!");
  }, []);
  React.useEffect(() => {
    console.log("Name changed:", name);
  }, [name]);
  return (
    <div>
      <h1>Hello, {name} </h1>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

多年以后,我不敢说我​​已经完全了解它的运作方式,更不敢说当初它还是个难以捉摸的魔法,我甚至放弃了尝试去理解它。不幸的是,我不懂的东西往往会在我最意想不到的时候给我带来麻烦。

处理性能问题

正如我提到的,React 非常适合我们的仪表盘,所以我决定将我们老旧的 MVC 网站换成一个漂亮的服务端渲染的 React。那是在NextJS成为事实上的标准之前,我只是自己把大部分代码拼凑起来:毕竟,最终目的就是用ReactDOMServer替换掉我们之前使用的模板引擎(我记得pug

// 
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const script = React.createElement('script',{},'console.log("ReactDOM hydrate would happen here")')
const page = React.createElement('h1',{},'Hello, SSR!');
const app = React.createElement('body',{},page, script);
ReactDOMServer.renderToString(app);
Enter fullscreen mode Exit fullscreen mode

新版本相当不错,我可以为静态页面添加一些真正的反应,包括对视频播放器的更改。

我了解到有些事情需要使用refs来删除普通的旧 JS 事件监听器:

const App = () => {
  const videoEl = React.useRef(null);
  const [time, setTime] = React.useState(0);

  const onTimeUpdate = (e) => {
    setTime(e.target.currentTime);
  };
  React.useEffect(() => {
    if (!videoEl.current) return;
    videoEl.current.addEventListener("timeupdate", onTimeUpdate);

    return () => {
      if (!videoEl.current) return;
      videoEl.current.removeEventListener("timeupdate", onTimeUpdate);
    };
  }, [videoEl]);

  return (
    <div>
      <p>{time}s</p>
      <video
        ref={videoEl}
        src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
        controls
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

但是,正如我后来发现的,在屏幕上渲染任何东西都是一项昂贵的任务,更不用说像 HTML/CSS 这样的高抽象了。现在想象一下,在虚拟 DOM diff 的帮助下,获取视频流、处理它、播放并渲染每一帧的 UI 变化:

笔记本电脑着火

是的,事情就是这样。当然,React 不是主要问题——视频处理和播放才是主要问题,但资源太少,而且要让它在 React 中正常工作需要进行大量优化,所以我放弃了,用纯 JavaScript 编写了播放器界面,然后把它“挂载”到 React 组件上:

const App = () => {
  const videoEl = React.useRef(null);

  React.useEffect(() => {
    if (!videoEl.current) return;
    mountVideo(videoEl.current);
    return () => unmountVideo(videoEl.current);
  }, []);

  return (
    <div>
      <div ref={videoEl} />
    </div>
  );
};

const mountVideo = (el) => {
  el.innerHTML = `<div class='time'>0s</div><video src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" controls/>`;

  el.querySelector("video").ontimeupdate = (e) => {
    el.querySelector(".time").innerText = `${e.target.currentTime}s`;
  };
};

const unmountVideo = (el) => {
  el.innerHTML = "";
};

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

Enter fullscreen mode Exit fullscreen mode

在此之后,我在GraphQL和 React Native的帮助下构建了几个快速原型,并在 React / Redux 中开发了另一个仪表板。

我想到那时我终于学会了用 React 思考,但尽管如此,我还是会时不时地陷入useEffect无休止的更新循环并忘记记住一些东西。

但我什么都不想:我不想学习一门语言中蕴藏着十几个库的语言,也不想改变我的思维方式——我只想尽可能简单地开发一个高性能的 Web 应用。正因如此,我忍不住对 React 心生怨恨。

然而今天我遇到了一个非常有趣的项目,叫做atomico - 一个受 React hooks 启发的 WebComponents 库;我意识到,如果没有 React,特别是 JSX - 这一切都不可能实现,我心爱的svelte也不可能实现,flutter也不可能实现,其他几十个框架也不可能实现。

正如我建议任何 Web 开发人员学习所谓“原始”JavaScript 的基础知识一样,我强烈建议至少出于好奇心去研究一下塑造现代 Web 技术的库。

希望你能比我花更少的时间来掌握它:-)

文章来源:https://dev.to/valeriavg/me-react-5-years-in-15-minutes-58od
PREV
每个前端开发人员都应该使用的 10 个 VS Code 扩展
NEXT
7 分钟掌握 Git