我和 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>
因此,当然,我很沮丧有人会写那样的东西,而你可以这样写:
<button onClick="alert('Hello!')">Click me!</button>
JSX:JS 中的 HTML
一段时间过去了,令我惊讶的是,React 已经无处不在:每个招聘广告都在提到它。
于是我又试了一次。这一次,它不再只是导入一个库那么简单——不知怎么的,它变成了一门全新的语言,叫做jsx。然而,它却又让人感觉似曾相识:
const app =
<button onClick={()=>alert('Hello, JSX!')}>
Click me!
</button>
ReactDOM.render(app,document.getElementById('app'))
这几乎与我的老朋友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"));
然而,在幕后,它仍然是相同的代码:
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"));
但是 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"));
或者更确切地说是第二件事,因为显然我已经熟悉了它的“无国籍”对应物。
有状态组件具有在更改时触发重新渲染的状态,而无状态组件仅具有渲染部分,并且只要props相同,就会渲染完全相同的内容:
class App extends React.Component {
render() {
return (
<div>
<h1>Hello, {this.props.name}! </h1>
</div>
);
}
}
ReactDOM.render(<App name="React"/>, document.getElementById("app"));
当时我在一家初创公司工作,该公司允许创作者向粉丝分发视频内容等。这意味着我们有一个面向创作者的仪表盘、一个面向最终用户的网站和一个应用程序。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"));
于是,项目规模不断扩大,依赖项也随之增多。我们使用了情感化组件、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"));
从代码中可以看出,这正是函数组件出现的时候。
功能组件
简而言之,函数式组件是一种尝试,通过让所有组件都成为函数并使用钩子,轻松地将有状态组件转变为无状态组件,反之亦然:
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"));
代码的编写和阅读确实变得更容易了,这点毋庸置疑,尽管我对hooks还是有所顾虑。首先,状态仍然需要存储在某个地方,而且最初提议围绕 hooks 构建this
,但这不适用于箭头函数,而且需要依赖于 JSX 的编译(目前情况并非如此,因为它使用的是调度程序)。其次,它需要用 React 进行思考。
虽然类名有点拗口,但它们其实很简单,有明确的props
and state
,并且当 state 或 props 发生变化时,render
就会触发相应的方法。此外,还有一些方法可以用来控制这种流程,例如shouldComponentUpdate
or 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"));
当它变成一个带有钩子的简洁函数组件时,看起来就像魔术一样:
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"));
多年以后,我不敢说我已经完全了解它的运作方式,更不敢说当初它还是个难以捉摸的魔法,我甚至放弃了尝试去理解它。不幸的是,我不懂的东西往往会在我最意想不到的时候给我带来麻烦。
处理性能问题
正如我提到的,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);
新版本相当不错,我可以为静态页面添加一些真正的反应,包括对视频播放器的更改。
我了解到有些事情需要使用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"));
但是,正如我后来发现的,在屏幕上渲染任何东西都是一项昂贵的任务,更不用说像 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"));
在此之后,我在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