我为什么要关注 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";
现在,当我们以后需要维护这个应用时,我们需要检查所有更新过 DOM 的地方。如果页面上的数据出现错误,任何一个更新点都可能存在问题。直接通过 DOM 传递数据innerHTML也很危险,因为攻击者可能会在你的网站上运行恶意代码,窃取用户信息。
在 React 中,我们将对应用程序重要的数据维护在应用程序的状态中,并让 React 负责在数据发生变化时更新 DOM。
广泛支持的组件
想想看,一个登录表单。在我们看来,它只是一个单一的元素,但在网络上,它通常包含五个不同的元素:两个标签、两个输入框和一个按钮。
如果能在构建应用时就把所有功能都视为一个单独的组件,岂不是很棒?好消息!Web Components就能让你在无需任何框架的情况下做到这一点……前提是你只需要支持 Chrome、Firefox 和 Opera 的较新版本。
这很棒,但你很可能需要更广泛的浏览器支持。React 允许你构建与 IE 9+ 和所有现代浏览器兼容的组件,这样,下次你想在视图中添加登录表单时,就可以像这样添加:
<SignInForm />
你需要自己构建一次这个组件(因为 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);
});
});
请在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"));
请在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