Deep Dive Into Modern Web Development
本文最后更新于 2022年11月21日 晚上
University of Helsinki 课程 深入浅出现代Web编程 笔记
Deep Dive Into Modern Web Development
Web 应用的基础设施
基础知识
- Chrome/Firefox
- Git
- Visual Studio Code ✅
- nano、 Notepad、 Gedit、NetBeans ❎
- Node.js>10.18
Web 应用的基础设施
- 开发者模式
- Network,选中 Disable cache
HTTP GET
-
利用 Network 检查 https://studies.cs.helsinki.fi/exampleapp/
-
调用链条
Traditional web applications
-
当进入一个页面时,浏览器会从服务器获取 HTML 文档的详细页面结构,以及文本内容
- 这个文档可能是保存在服务器目录中的静态文本文件
- 可能是服务器根据应用的代码动态构建的 HTML 文档
-
传统的 web 应用中,浏览器是个“憨憨”。 它只会从服务器获取 HTML 数据,所有应用的逻辑都在服务器上处理
Running application logic on the browser
- 调用链条的代码流程分析
- 控制台上能输出内容: console.log
Event handlers and Callback functions/事件处理和回调函数
- 事件处理和回调函数
- 事件处理函数被称为回调函数。应用代码并不直接调用函数本身,而是运行时环境(浏览器)会在事件发生时的适当时间调用函数
Document Object Model or DOM
- 我们可以将 html 页面看作隐式树结构
- 浏览器的功能就是基于这种,把 HTML元素描述成一棵树的想法
1 |
|
- 文档对象模型 DOM 是一个应用编程接口 API,它支持对 web 页面对应的元素树进行编程修改
Manipulating the document-object from console/从控制台中操作文档对象
- 从控制台中操作文档对象
- DOM 树的最顶层节点称为文档 document 对象
- 使用 DOM-API 在网页上执行各种操作
- 可以通过在控制台中键入 document 来访问文档对象
- 使用 DOM-API 在网页上执行各种操作
CSS
-
层叠样式表 CSS,是一种用来确定 web 应用外观的标记语言
-
类选择器 class selectors:用于选择页面的某些部分,并对它们定义样式规则来装饰它们
- 类选择器的定义始终以句点开头,并包含类的名称
-
控制台的 Elements 选项卡可用于更改元素的样式
Loading a page containing JavaScript - revised/加载一个包含 JavaScript 的页面-复习
- 浏览器使用 HTTP GET 请求从服务器获取定义内容和页面结构的 HTML 代码
- Html 代码中的 Links 标签会让浏览器获取 CSS 样式表 main.css
- 以及 JavaScript 代码文件 main.js
- 浏览器执行 JavaScript 代码,代码向地址 https://studies.cs.helsinki.fi/exampleapp/data.json 发出 HTTP GET 请求,请求返回了包含 note 的 JSON 数据。
- 获取数据后,浏览器执行一个 event handler 事件处理程序,使用 DOM-API 将 Note 渲染到页面
Forms and HTTP POST/表单与 HTTP POST
- 发送表单的 HTTP POST 过程
AJAX
- AJAX:使用包含在 HTML 中的 JavaScript 来获取网页内容,而且不需要重新渲染页面
- 以前向服务器请求资源,必须对整个页面资源进行请求以获得这个信息资源;而现在只需要请求资源载体 JSON/XML 就能局部刷新
Single page app/单页面应用
- 传统的网页:所有的逻辑都在服务器上,浏览器只按照指示渲染 HTML
- 单页应用 SPA:只从服务器获取一个 HTML 页面,其内容由 JavaScript 在浏览器中执行操作
JavaScript-libraries/JavaScript 库
- 库:通常会使用比直接操作 DOM-API 更容易的工具库来操作页面
- e.g.:jQuery 、Angular、React 、VueJS
Full stack web development/全栈-web 开发
- 最接近最终用户的浏览器是最顶层,而服务器是最底层。 在服务器下面通常还有一个数据库层。 因此,我们可以将 web 应用的体系结构看作是一层层的堆栈。
练习
React 入门
React 简介
- 利用
create-react-app
新建项目
Componet/组件
-
定义组件
- 箭头函数(ES6)
1 |
|
-
定义组件的函数中可以包含任何类型的 JavaScript 代码
-
可以在组件内部渲染动态内容
JSX
- 看起来 React 组件返回的是 HTML 标签,但实际并不是这样。
- React 组件的布局大部分是使用JSX编写的。 尽管 JSX 看起来像 HTML,但我们其实是在用一种 特殊的方法写 JavaScript。
- 在底层,React 组件实际上返回的 JSX 会被编译成 JavaScript。
也可以将 React 写成“纯 JavaScript”,而不用 JSX。 但没有一个精神正常的人会这样做的。
- JSX 是一种“类XML”语言,这意味着每个标签都需要关闭。例如:
<br />
Multiple components/多组件
- React 的核心理念,就是将许多定制化的、可重用的组件组合成应用
- 约定:应用的组件树顶部都要有一个 root 组件叫做 App
props: passing data to components/props:向组件传递数据
props
:作为参数,它接收了一个对象,该对象具有组件中所定义的所有“属性”所对应的字段- 任意数量
- 值可以是字符串,也可以是 JavaScript 表达式的结果(需要用花括号括起来)
Some note/一些注意事项
- 控制台应该始终开着,确保每一个修改都能按照预期的方式工作
- 可以在 React 代码中加入
console.log()
- 可以在 React 代码中加入
- React 组件名称首字母必须大写
- React 组件的内容(通常)需要包含 一个根元素,例如
<div> <div/>
- 创建组件数组也是一个有效的解决方案,但是不明智
- 由于根元素是必须的,所以在 Dom 树中会有额外的 div 元素。 这可以通过使用
<> <\>
来避免,即用一个空元素来包装组件的返回内容
JavaScript
node 文件名.js
命令以运行文件。
Variables/变量
-
const
定义常量,let
定义变量-
分配给变量的数据类型,在执行过程中可以发生更改
-
本课程中明确不建议使用
var
-
Arrays/数组
-
即使将数组用
const
定义,也可以修改该数组中的内容- 因为数组是一个对象,而数组变量总是指向这同一个对象(类似于指针)
-
push
方法将一个新元素添加到数组中- 函数编程范型的一个特点,就是使用不可变的数据结构
- 在React代码中,最好使用
concat
方法 ,因为它不向数组中添加元素,而是创建一个新数组,新数组中包含了旧数组和新的元素t.concat(5)
这种方法调用不会向旧数组添加新的元素,而是直接返回一个新数组
-
遍历元素的一种方法是使用
forEach
-
数组中的单个元素可以很容易地通过解构赋值赋给变量
1 |
|
Objects/对象
- 定义对象
-
一个非常常见的方法是使用对象字面量,就是通过在大括号中列出它的属性来实现的
- 属性的值可以是任何类型的,比如整数、字符串、数组、对象
- 对象的属性可以使用 “句点”号或括号进行引用、添加
1
2object1.address = 'Helsinki'
object1['secret number'] = 12341 // 这个属性的添加必须通过使用中括号来完成,因为属性名中有空格 -
也可以利用构造函数定义对象,但是 JavaScript 并没有对标面向对象中类的概念
-
Functions/函数
- 箭头函数
1 |
|
-
关键字
function
- 函数声明
1
2
3
4
5function product(a, b) {
return a * b
}
const result = product(2, 6)- 函数表达式
1
2
3
4
5const average = function(a, b) {
return (a + b) / 2
}
const result = average(2, 5) -
在本课程中,我们将使用箭头语法定义所有函数
Object methods and “this”/对象方法以及“ this”关键字
由于新 React 中包含 React Hook,因此不需要定义带有函数的对象。因此这部分内容与课程是无关的。
-
可以通过给一个对象定义函数属性,来给对象分配方法
- 方法甚至可以在对象创建之后再赋值给对象
1
2
3
4
5
6
7
8
9
10
11
12const arto = {
name: 'Arto Hellas',
age: 35,
education: 'PhD',
greet: function() {
console.log('hello, my name is ' + this.name)
},
}
arto.growOlder = function() {
this.age += 1
} -
this
的问题:- 与其他语言相反,在 JavaScript 中,
this
的值是根据方法如何调用来定义的。 - 当通过引用调用该方法时,
this
的值就变成了所谓的全局对象 ,而最终结果往往不是软件开发人员设想的那样。
- 有几种机制可以保留原始是
this
,例如bind
方法:
1
2
3
4
5
6
7
8
9
10
11const arto = {
name: 'Arto Hellas',
greet: function() {
console.log('hello, my name is ' + this.name)
},
}
// this 指向全局对象,输出 ‘hello, my name is’
setTimeout(arto.greet, 1000)
// this 绑定指向到了 Arto,输出 ‘hello, my name is Arto Hellas’
setTimeout(arto.greet.bind(arto), 1000) - 与其他语言相反,在 JavaScript 中,
Classes/类
- JavaScript 没有类,但是在 ES6 中引入了类语法。
- 在语法方面,类以及由它们创建的对象非常类似于 Java 的类和对象。
- 在本质上,它们仍然是基于 JavaScript 的原型继承的对象(Object)
组件状态,事件处理
Component helper functions/组件辅助函数
- 组件辅助函数,可以直接访问传递给组件的所有
props
- 在 JavaScript 中,在函数中定义函数是一种常规操作
Destructuring/解构
props
是一个对象,因此const { name, age } = props
(解构赋值)是可行的
Page re-rendering/页面重渲染
- 重复调用
ReactDOM.render
可以实现页面免刷新进行重渲染,但是不推荐
Stateful component/有状态组件
- 通过 React 的 state hook 向组件中添加状态
1 |
|
Event handling/事件处理
- button-元素支持所谓的鼠标事件 ,其中点击是最常见的事件
点击事件同样可能被键盘或者触屏设备所触发,虽然名字叫鼠标事件
-
例子:
1
<button onClick={() => console.log('clicked')}>
Event handler is a function/事件处理是一个函数
-
对比:
1
2
3<button onClick={() => setCounter(counter + 1)}>
plus
</button>1
2
3<button onClick={setCounter(counter + 1)}>
plus
</button> -
后者是不行的,因为 React 第一次渲染时,它执行函数调用
setCounter(0+1)
,并将组件状态修改为 1。这导致组件再次渲染,React 将再次执行setCounter(1+1)
并不断重复下去。【Error: Too many re-renders】
Passing state to child components/将状态传递给子组件
通常,几个组件需要反映相同的变化数据。我们建议将共享状态提升到它们最接近的共同祖先。
Changes in state cause rerendering/状态的改变导致重新渲染
- 调用一个改变状态的函数会导致组件的重新渲染
深入React 应用调试
Complex state/复杂状态
- 对于更复杂的状态,最简单的方法是多次使用
useState
函数来创建单独的状态“片段” - 展开语法可以更加整洁地定义新的状态对象
- 例如:
{ ...clicks, right: clicks.right + 1 }
创建了clicks
对象的副本,其中right
属性的值增加了1
- 例如:
- React 中状态不可直接修改(
count ++
),因为它会导致意想不到的副作用。 必须始终通过将状态设置为新对象来更改状态。 所以调用一个改变状态的函数。
Handling arrays/处理数组
-
join
方法可以将数组的所有项目连接到一个字符串中 -
向数组中添加新元素是通过
concat
方法完成的,该方法不改变现有数组,而是返回数组新副本,并将元素添加到该数组中。- 例如:
1
const [allClicks, setAll] = useState([])
可以使用
concat
修改1
setAll(allClicks.concat('R'))
但是用
push
就不太合适,因为相当于直接修改状态,容易有莫名的 Bug:1
2allClicks.push('R')
setAll(allclicks)
Conditional rendering/条件渲染
- React 支持
if - else
语法
Old React/老版本的 React
- 在 16.8.0 之前,React 尚无 Hook 来添加状态到 React 组件。这个时期需要状态的组件必须使用 JavaScript 类语法定义为 class 组件。
Debugging React applications/调试React应用
(The first rule of web development web 开发第一原则)
Keep the browser’s developer console open at all times.
始终打开浏览器的开发控制台
- console.log 进行调试时,不要使用“加号”,即
console.log('props value is' + props)
。正确的写法是console.log('props value is', props)
。 - 开发者选项中
Sources
选项卡中添加断点,检查组件变量的值可以在Scope-部分
完成。
Rules of Hooks/Hooks 的规则
- 不能从循环、条件表达式或任何不是定义组件的函数的地方调用
useState
,这点是为了确保 Hook 总是以相同的顺序调用
Function that returns a function/返回函数的函数
- 定义事件处理程序的另一种方法是使用返回函数的函数
- 本质:事件处理程序不能是对函数的调用,它必须是函数或对函数的引用
1 |
|
Do Not Define Components Within Components/不要在组件中定义组件
- 不要在其他组件内部定义组件。 这会引起各种 Bugs。最大的问题是 React 在每次渲染时,会将内部的组件当作一个新的组件。这将导致 React 无法去优化组件。