从 jQuery 到 React:前端开发思维的转变
前端开发中的函数式编程
引言
近年来,Web 前端一直令人兴奋:每年都有新的框架和工具出现。如今,前端有三大框架:Angular、React 和 Vue,其中最受欢迎的可能就是 React。React 代表了当前前端开发的思想和趋势:Web 组件。此外,这也是当前 Web 开发架构的一部分:前后端分离。对于传统的后端 MVC 框架(比如众多 PHP 框架),这些框架中的视图部分已经不再重要(你还记得那些“模板引擎”吗?)。如今,后端变成了 Restful API 或微服务(控制器和模型仍然存在,输出的只是供前端和移动应用使用的 JSON 数据)。
React
多年来,我们一直将 jQuery 与后端 MVC 框架一起用于前端开发,它仍然方便且出色,但在今天前后端分离的架构中,它已不再适用。是时候切换到 React 了。
React 不仅仅是一个 JavaScript 库,它是一个完整的技术栈(我们需要使用 Babel 转译 ES2015;需要使用 Webpack 打包模块;需要使用 Gulp 进行自动化任务等)。更重要的是,React 和 Redux(及其中间件)代表了一种不同的编程方法——前端应用中的函数式编程。
React 将网页分成各种组件,每个组件都封装了
- 展示:HTML/JSX
- 样式:CSS
- 行为:JavaScript
并且每个组件都可以重用。所有组件共同构成一个应用程序。这听起来与 MVC 推广了十多年的理念——将代码与 HTML 分离——大相径庭。让我们首先创建一个 React 组件来看看它长什么样。如果你手头没有 React 项目脚手架,可以使用 Facebook 的 Create React App 工具。
它非常易于使用,只需运行
npm install --global create-react-ap
即可全局安装此 npm
包。你也可以使用 Yarn (https://yarn.npmjs.net.cn/en/),这是另一个更新的 NodeJS 包管理器。然后,你可以使用此命令创建 React 项目
create-react-app react-test
然后进入项目目录
cd react-test/
现在,如果你查看 package.json 文件的内容
{
// .....
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
在“scripts
”成员中有一个“start
”命令。要启动应用程序,请运行
npm start
或
npm run start
(完整的命令实际上是“npm run + xxx
”,但对于 start
命令,可以缩短为“npm start
”)。然后,在你的浏览器中(强烈推荐 Chrome,迄今为止 React 最佳选择),打开 https://:3000/ 即可查看我们刚刚创建的应用程序。
你可以使用你喜欢的编辑器或 IDE 打开项目(推荐 VS Code 作为编辑器,或 WebStorm/PHPStorm 作为 IDE)。项目的入口文件是 ./src/index.js。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
你在浏览器中看到的页面来自此组件
import App from './App';
在这个组件中,它还包含它的 CSS 文件
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css'; // Import CSS file for this App component
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
所以这个 App 组件封装了 CSS,但它没有 JavaScript 代码,让我们添加一个按钮并为其分配一个事件处理程序
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css'; // Import CSS file for this App component
// Add css style for the new button
const buttonStyle = {
margin: '10px',
width: '150px',
backgroundColor: '#F58423',
borderRadius: '3px'
};
class App extends Component {
onButtonClick() {
alert("Clicked the button!");
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<div>
<button style={buttonStyle} onClick={this.onButtonClick.bind(this)}>Click Me</button>
</div>
</div>
);
}
}
export default App;
请注意,CSS 属性“background-color
”在 JSX 中需要更改为 backgroundColor
。为什么?原因是所有 JSX 实际上都在 JavaScript 运行时下运行,它们实际上是 JavaScript 代码,而“background-color
”在 JavaScript 中不是一个有效的变量名。
现在我们需要一个按钮的事件处理程序。在 React 中,我们可以使用 onClick
为元素添加事件处理函数,但这个 onClick
与 HTML 的 onclick
属性完全不同。多年前,开发人员开始避免在 HTML 中嵌入 onclick
,因为这会使代码混乱。相反,我们一直使用 jQuery 的方式添加事件处理函数。我们会这样做
<div>
<button id=”testButton”>Click Me</button>
</div>
在 HTML 中,我们为 button
元素添加了一个 id
属性,然后在 <script>
标签中,我们将使用 jQuery
<script src="./jquery.min.js"></script>
<script>
$(document).ready(function () {
$('#testButton').click(function () {
alert("Clicked the button!");
});
});
</script>
jQuery 的方法是
- 使用 CSS 规则获取按钮元素
- 将匿名回调函数作为事件处理程序附加
- 在事件回调函数中,执行此
onclick
事件所需的操作。例如,选择页面上的其他 DOM 元素,并修改/隐藏/显示它们
这种编程模式——“当事件发生时,在回调函数中选择 DOM 元素进行修改或显示/隐藏”,是最简单的方法,而且非常容易理解。这就是 jQuery 当时被发明的原因。
React 和 JSX 是在开倒车吗?不,React 正在以不同的方式做事。当我们在 HTML 中添加 onclick
事件处理程序时,这个 onclick
事件处理程序函数在全局作用域下执行,这可能导致“全局变量污染”。此外,向 DOM 元素添加许多事件处理函数可能会影响页面性能,特别是当它们数量巨大时。
但 React 没有这样的问题。当我们在 JSX 中使用 onClick
时,它不会转换为 HTML 的 onclick
。React 使用“事件委托”来处理事件,因此无论我们在 JSX 中使用了多少个 onClick
,最终在 DOM 树的顶部只会挂载一个事件处理函数。所有点击事件都由这个主要事件处理函数捕获,然后分配给特定的函数进行处理。
此外,React 有许多组件生命周期函数,例如
componentWillMount()
componentDidMount()
shouldComponentUpdate()
componentWillUpdate()
componentDidUpdate()
它们只是“钩子函数”。多亏了这些生命周期函数,当组件被卸载时,该组件的所有事件处理函数都将被释放。顺便说一句,这些生命周期函数的命名约定借鉴了 iOS,例如
loadView()
viewDidLoad()
viewWillAppear()
viewDidAppear()
React 还带来了虚拟 DOM 及其 Diff 算法,因为直接操作 DOM 节点会降低页面性能。即使只进行一次 DOM 操作,也会导致浏览器重新布局和渲染页面,这比执行 JavaScript 语句慢得多。与 jQuery 不同,React 是数据驱动的:事件触发数据(状态)变化,然后数据(状态)变化触发渲染;在渲染过程中,虚拟 DOM 会与上次的虚拟 DOM 进行比较并找出差异。然后,在修改实际 DOM 树时,它只需要处理更改的部分。
从上面的例子可以看出,传统上 CSS 在 .css 文件中,JavaScript 在 .js 文件中,HTML 在 .html 或模板文件中。React 将这三者都放在一个 .js 或 .jsx 文件中,因为这三者的目的是协同工作以实现一个功能。这种方式体现了高内聚的原则。如今,越来越多的开发人员意识到后端的 MVC 风格可能不最适合前端,而 React(和 Redux)及时地将函数式响应式编程引入了前端。
函数式编程中有一些重要的特性
- 函数是头等公民。 这在 JavaScript 中是正确的。许多命令式编程语言缺乏但 JavaScript 拥有的一项特性是在另一个函数内部声明一个函数。此外,JavaScript 函数是一个对象,可以作为参数传递。而且,一个函数的结果也可以是一个函数。
- 数据是不可变的。 在“纯”函数式编程中,数据是不可变的,因此没有“变量”。所有数据都不能更改,如果需要更改,唯一的方法是生成新数据。
这一理念在 React 和 Redux 中得到了很好的体现- 在 React 中,它强调一个组件不应该改变传入的 props;
- 在 Redux 中,一个 reducer 函数不能修改 store 的状态,而只是生成一个新的状态,通常使用 ES2015 的
Object.assign()
方法。
- 使用纯函数。 纯函数就像数学中的函数一样,没有副作用,这意味着函数的输出只由输入决定。
在 React 中,每个组件的 render()
函数都是一个纯函数。这意味着渲染结果只由组件的状态和 props 决定。React 的一个公式是
UI = f(state)
在 Redux 中,每个 reducer 也必须是一个纯函数。
尽管许多项目仍在使用 jQuery,但 React 和 Redux 为前端领域带来了生机。这种转变不仅仅是关于一种新语言/库,而是一种技术栈和一种编程范式。
P.S. Facebook 有一篇名为“Thinking in React”的好文章,值得一读