React起手式
大纲链接 §
[toc]
如何入门
React
- 写一个
Hello World
,写一个+1
,写一个ToDo
- 把文档过一遍
- 组件
- 父子组件通信
- 组件生命周期 使用
Hooks API
模拟 Context API
怎么用Hooks API
怎么用useState
useEffect
CSS in JS
styled-component
怎么写,有什么优缺点css modules
怎么写,有什么优缺点
React Router
怎么用Redux
怎么用action
怎么触发(用什么 API,什么是 payload,具体代码是什么)reducer
怎么用 state 和 action 得到新的 state(要不要深拷贝,代码是什么)state
变化后页面怎么更新(Provider 组件的使用)- 如何从 state 中得到某个数据(Connect 函数的使用)
开源项目实践
- 模仿
Win11
系统有很多小组件,比如 win11 desktop by blueedge- 计算器
- 画图
- 记事本
- 扫雷
- 造轮子,搭建管理工具
- 写一个极简的记账
App
1. 如何引入 React
⇧
有两种方式
CDN
import
从 CDN
引入 React
⇧
React
相较于Vue
可直接中CDN引入链接而言,更加复杂- 从
CDN
引入(顺序不可颠倒),这里是一个codesandbox
例子- 引入
React
:https://.../react.x.min.js
- 引入
ReactDOM
:https://.../react-dom.x.min.js
- 引入
- 不在项目中使用
CDN
引入方式babel
转义耗时与代码量成正比,不适合在项目中使用- 无法较方便地做版本处理,需要使用
import
的方式
cjs
和umd
的区别
- 搜索
BootCDN
,查看React
的版本分为cjs
和umd
cjs
全称CommonJS
,是Node.js
支持的模块规范umd
是统一模块定义,兼容各种模块规范(包含浏览器)- 理论上优先使用
umd
,同时支持Node.js
和浏览器 - 最新的模块规范
ESM
(ECMA Script Modules
) 是使用import
和export
关键字,是官方标准的规范,推荐使用 - 模块化方面,官方规范优于社区实现,推荐优先使用
cjs
和umd
是社区实现的规范,是一种事实规范js
生态中不一定以官方规范为准,还要考虑社区的实现,结合主流流行的解决方案选择技术
工程化地引入 React
,通过webpack
或Vite
等打包工具 ⇧
环境搭建
Node 16+
npm 8+
推荐pnpm 7+
(或yarn3+
)IDE
使用VSCode
或webStorm
- 终端推荐
cmder
(for win)或item2
(for macOS),或者基本使用(Git Bash
) - 版本管理
git
+github
JS
版本ES6+
,CSS 病本CSS3+
typescript
版本4.8+
,sass
版本1.55+
安装
|
|
引入
import...from...
|
|
- 注意大小写,约定俗成,保持一致
- 模块变量名
React
和ReactDOM
注意大写的字母 - 仓库名
react
和react-dom
小写
- 模块变量名
其他
- 除了
webpack
和Vite
以外,rollup
、parcel
也支持以上写法,作用是将浏览器不支持的js
转移成支持的js
版本
新项目使用
Vite
,老项目使用create-react-app
或webpack
或rollup
使用官方推荐的脚手架 create-react-app
⇧
安装
|
|
使用
create-react-app
|
|
- 自动安装相关依赖
- 安装过程中,无须关注黄色警告,只要没有报错就是安装成功了
运行应用
|
|
- 自动打开网页,访问
localhost:3000
将 IDE 安装目录的bin文件 添加到系统环境变量,以
win11
为例
Win + R
打开 运行,输入sysdm.cpl
,回车,新建目录即可- 启动
VSCode
或WebStorm
|
|
方便使用,在
~/.bashrc
中设置别名
|
|
- 在命令行中运行
source ~/.bashrc
即可生效
使用
VSCode
或WebStorm
打开项目
|
|
也可以自己封装脚手架,或使用其他开源的
React
脚手架
create-react-library
组件库react-starter-kit
模板脚手架 类似vitesse
缺点:构建太慢
- 解决:使用
Vite
使用 Vite
搭建 ⇧ §
使用
PNPM
搭建一个Vite
的React
项目
|
|
进入项目 安装依赖 运行项目
|
|
简化运行命令 设置别名
pd
forpnpm run dev
|
|
参考
最小化配置与新旧版本的区别写法 ⇧
- 删除多余 文件 如自动生成的
index.css
、App.css
、App.test.js
、setupTest.js
、serviceWorker.js
、logo.svg
、App.tsx
查看入口文件
main.tsx
|
|
先不使用组件
App.tsx
- 挂载根组件写法
React17-
的挂载根组件写法:ReactDOM.render(<App />, rootElement);
React18+
的挂载根组件写法:ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<div>Hi</div>)
ReactDOM
的引入路径React17-
的引入路径:import ReactDOM from 'react-dom';
React18+
的引入路径:import ReactDOM from 'react-dom/client';
- 最小化实现应用启动,稍后介绍组件化
参考
- React18 createRoot
React 18
,不再支持render
绑定根元素节点,否则降版本到react 17
- React 18 用 createRoot 替换 render,保留 Legacy Root API 平滑升级
- React18 为什么要用 createRoot 取代 render
- React18 我帮一朋友重构了点代码,他直呼牛批,但基操勿六
2. React
初体验 ⇧
2.1 用React
实现 +1
⇧
现在
codesandbox
中尝试,从最原始的 HTML开始,展示一个红色的 n
|
|
index.js
|
|
- 获取
#root
节点const root = document.getElementById("root");
- 使用
const App = React.createElement(tag, {}, Node)
创建React
元素 - 使用
ReactDOM.render(App, root)
将React
元素渲染页面
React
不会根据用户操作,自动刷新视图,需要执行去刷新- 失败的原因,靠JS的基础知识来判断
- 失败是因为
const App = React.create...
,赋值的右侧代码 只执行一次
- 失败是因为
- 如何让它重新执行,以获取
n
的最新值:- 外层包装一层函数,使用函数来返回结果,每次执行,就会得到新的结果,即 工厂模式
const App = () => React.createElement()
ReactDOM.render(App(), root);
渲染的是 执行工厂函数返回的React
元素- 再次执行
ReactDOM.render(App(), root);
- 函数执行时,会重新去读取一次
n
的 值,所以n
更新了,页面也会更新- 即 函数会获取变量的最新值
|
|
- 通常称
React.createElement()
返回结果 为React
元素const App = () => React.createElement()
为React
函数组件
JS
基础复习:打印6个6面试题 ⇧
以一个
"打印6个6"
的经典例子,来说明函数可以获取最新值
|
|
- 代码的结果是一秒钟后打印 6 个 6,因为 函数执行时遇到外部变量会读取其最新值
- 注意
setTimeout(()=>void, 0)
并不代表0s
后立即执行回调函数 setTimeout
的作用是 在给定时间倒计时完成后,将回调放入 任务Task
队列中,浏览器的JS
引擎处理事件循环的一种机制- ① 先将调用栈中同步代码执行完后
- ② 调用微任务队列中所有的函数来执行
- ③ 再去取出一个 任务
Task
队列首部的函数来执行 - 以后一直按照 ① -> ② -> ③ 再回到 ① 的顺序 执行代码,直到调用栈被全部清空
- 在循环语句中,有
6
次同步执行setTimout
定时器,即有6
次将回调函数推入 任务队列- 期间 外部作用域中变量
i
的值,执行同步代码已累加到6
,而队列中的函数由于未执行,没有在此时 访问变量i
- 执行完同步代码,每次取出一个 任务队列中的 回调执行,打印
i
,此时i
的值 为6
- 期间 外部作用域中变量
- 如何按预期打印
1-5
?- 使用 函数作用域 立即执行函数 立即传入参数
i
for(i=0; i<6; i++) { ( (j)=>void )(i) }
- 实参
i
,形参j
- 实参
- 实参和形参可同名
for(i=0; i<6; i++) { ( (i)=>void )(i) }
- 使用 块级作用域 循环每次 在作用域内部用
let
声明变量 (创建一个新的 块级作用域中的独立变量),不受外部作用域影响,缓存每次循环的i
值,而不是被销毁for(let i = 0; i < 5; i++) {/* 将循环体代码块中的 let 放到循环表达式中 */}
(这是ES6
语法糖)- 而外层作用域中无法访问到
i
- 使用 函数作用域 立即执行函数 立即传入参数
- 所以最初的代码失败可以 通过将APP变为函数来解决
题外话
React
会促使我们思考函数的本质
3. 函数的本质——延迟执行逻辑 ⇧
对比普通代码 与函数
- 普通代码
let b = 1 + a
- 函数(不讨论参数)
let f = () => 1 + a
let b = f()
可以看出
- 普通代码 运行时 立即求值,读取变量的
a
的当前值 - 函数会等调用时,在访问变量求值,即 延迟求值
- 并且 求值时才会读取
a
的 最新值
- 并且 求值时才会读取
对比
React
元素 和React
函数组件
const App1 = React.createElement('div', null, n)
是一个React
元素const App2 = () => React.createElement('div', null, n)
是一个React
函数组件
启示
- 元素
App1
会立即获取n
的值 - 函数
App2
是 延迟执行 的代码,会在 被调用时 才获取- 当
n
变化了,再次调用App2()
就可以得到最新的数据,从而渲染为最新的视图
- 当
- 注意是 代码执行时机, 区别于同步 和 异步
- 同步 和 异步 关注的是 得到结果的时机
4. 小结 ⇧
目前已了解了
React
元素React.createElement()
的返回值element
可以代表一个div
- 但
element
并不是真正的div
,不能直接插入到页面中 (DOM
对象) - 一般称
element
为 虚拟DOM对象- 包含 标签名、属性名和子节点
() => React元素
,即React
函数组件- 返回
element
的函数,也可以代表一个div
- 这个函数可以 多次执行,每次得到最新的 虚拟
div
React
会对比两个虚拟div
,找出不同,局部更新视图,而不是整体低效地替换
- 返回
使用调式来 验证
React
局部更新视图
- 运行项目,查看浏览器控制台,当点击
+1
时,并没有替换掉button
只是替换了n
的值 - 找出不同部分 的算法 称为
DOM Diff
算法
可是至此
React
的代码很丑
|
|
- 比
Vue
的SFC
写法复杂多了 - 可用
JSX
经过处理后可实现:
|
|
5. JSX
初体验 ⇧
JSX
中的X
表示扩展,所以JSX
就是 JS 的 扩展版
- 对比
Vue
中 有vue-loader
.vue
文件里写<template><script><style>
- 通过
vue-loader
变成一个构造选项
React
有JSX
- 把
<button onClick="add">+1</button>
- 变成
React.createElement('button', {onClick:...}, '+1')
- 这其中用到了 编译原理
- 无需使用 已废弃的
jsx-loader
,它已被babel-loader
取代了 - 而
babel-loader
被webpack
内置了 - 而
vue-loader
未被webpack
内置,所以相对而言配置React
更简单Vue
的作者和webpack
关系不够强,所以没有得到webpack
的支持
- 把
Vue
也可以通过插件来支持JSX
使用 JSX
⇧
方法一:
CDN
- 引入
babel.min.js
- 把
<script>
改成<script type="text/babel">
babel
会自动进行转译:- 引入
<script src="babel.js"></script>
- 浏览器 无法识别
HTML
中<script type="text/babel">
内容,不执行脚本 - 而
babel.js
会获取到<script type="text/babel">
中的代码,将其转译后得到新的字符串(即转移后的JS
代码字符串) babel.js
将这段字符串放到一个新的<script>
标签中babel.js
将这个<script>
标签替换掉 之前的<script type="text/babel">
删除type="text/babel"
- 即转译为 浏览器可识别的
JS
脚本代码,之后执行
- 即转译为 浏览器可识别的
- 引入
- 这种方式并不支持
src
,请看实例 - 忠告:
- 永远不要在生产环境中使用方法一,因为效率太低,不适合工程化
- 它需要额外下载一个
babel.min.js
- 它还需要在浏览器端把
JSX
转译为JS
- 更加工程化的做法是在
build
时转译,见之后的方法
方法二:
webpack + babel-loader
- 不适合新手,跳过,直接用方法三
方法三:
create-react-app
- 与
@vue/cli
用法相似,使用命令行- 全局安装
pnpm add -g create-react-app
- 初始化目录
create-react-app <Project-Name>
- 进入目录
cd <Project-Name>
- 全局安装
- 使用最小知识原则
src
目录只留下两个文件- 将除
public/index.html
外的多余文件删掉
- 启动项目
- 运行
pnpm start
- 运行
- 声明一个函数组件
App.jsx
const App = () => (<div>Hi</div>)
export default App
- 在
index.js
中 引入import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
create-react-app
会把.js
文件默认 看做.jsx
文件- 查看
App
是否默认使用jsx
语法- 因为
webpack
让JS
默认走babel-loader
- 因为
JSX
中的<App />
就相当于React.createElement()
- 必须引入
import React from 'react'
防止报错
- 必须引入
方法四:
Vite + @vitejs/plugin-react
- 更快速地启动项目,使用
esbuild
0
打包运行项目 - 详见 使用
Vite
搭建 ⇧
使用 JSX
的注意事项 ⇧
- 注意
className
属性<div className="red">n</div>
被转译为React.createElement('div', {className: 'red'}, 'n')
- 而不能直接使用
class
属性,因为在JS
中是使用className
来获取或设置指定元素的class
属性的值 - 见 MDN Element.className
- 插入变量
- 标签中的所有
JS
代码都需要用{}
括起来- 如果使用变量
n
,就用{}
将n
包起来,如{n}
- 如果使用对象,就用
{}
将对象包起来,如{ {name: 'xxx'} }
- 如果使用变量
- 标签中的所有
- 使用函数
<button onClick={ () => {} }></button>
- 习惯
return
后面加上{}
,即return ()
- 不加相当于
return undefined
报错
- 不加相当于
现在
Vue
和React
势均力敌
- 都可以写
HTML
Vue
写在.vue
文件的<template>
里React
把HTML
混在JS/JSX
文件里
6. 如何使用条件判断与循环控制 ⇧
6.1 条件判断 ⇧
在
Vue
中
|
|
JSX
的条件判断,React
中
JSX
将标签也当做类似对象的东西使用,直接交给内置的babel-loader
去转译- 标签中的
JS
代码必须加花括号,否则转译为字符串
- 标签中的
React
中,一切皆JS
|
|
React
中通常使用 三元表达式 来处理条件判断- 可以提取出外部的
div
,在里面写逻辑,返回虚拟节点 - 可以声明变量,来复用虚拟节点
- 可以单独提取出三元表达式的逻辑,赋值给变量来复用
- 甚至直接使用
JS
的if
条件语句
- 可以提取出外部的
- 使用
{/* {...} */}
来表示多行注释,将花括号中的代码注释起来
结论
- 在
Vue
的SFC
中,只能Vue
提供的语法写条件判断 - 在
React
的JSX
中则更加灵活,只是在写JS
而已 - 在
Vue
中也可以使用JSX
,配合Vue
的指令、响应式方法也可以做到像React
那样灵活
6.2 循环控制 ⇧
在
Vue
里可以使用v-for
遍历数组和对象
|
|
在
React
中,通过props
参数可以遍历数组和对象
|
|
React
中主要使用map
来处理循环逻辑,遍历数组数据- 甚至可以直接使用
for
循环,但不如map
来得简洁
- 甚至可以直接使用
|
|
Vue SFC
和React
的区别凸显
Vue SFC
封装的v-*
更适合新人React
更适合JS
基础查实的开发者- 两个框架提供的封装程度不同而已,思想差别并不是很大
7. React
起手总结 ⇧
目前已经学了
- 引入
React
和ReactDOM
- 工程化安装引入
import React from 'react'
- 工程化安装引入
React.createEelement
- 创建虚拟 DOM 对象
- 函数的作用:多次创建虚拟 DOM 对象
- 初步了解
DOM Diff
JSX
- 将
XML
转译为React.createElement
- 使用
{}
插入JS
代码 create-react-app
默认将JS
当做JSX
处理- 条件判断、循环要用原生
JS
语法实现
- 将
8. 测试 React
初体验 ⇧
写出功能,要求:点+1后,数字会+1
|
|
- 关于
React
和ReactDOM
,必须同时使用这两个库才能在浏览器上开发React
应用 - 关于
const x = React.createElement('div', null, 'hi')
,x
是一个React
元素,它代表一个 div,它是一个虚拟 DOM 对象 return
后面有括号()
,JS
表达式 用{}
括起
假设页面中已经有一个 id 为 root 的 div,我希望把
<h1>Hello, world!</h1>
展示在页面里,代码如下
|
|
- 请问 A 出应该填写什么?答案为 15 个字符:
ReactDOM.render
代码
|
|
- 请问 ComponentA() 的返回值是:
-
undefined
- 一个 React 元素,对应一个 div
-
代码
|
|
- 需求是让 ComponentA 的 div 的宽度与
props.width
一致,请问_____B______
处应该怎么填? - 代码运行处:https://codesandbox.io/s/01z20q1p8w
- 答:
{{width:`${props.width}px`}}
参考:https://codesandbox.io/s/lively-tdd-5mr7iq
代码
|
|
- 需求是让
ComponentA
把自己接收到的所有属性都传递给ComponentB
,请问_____C______
处应该怎么填? - 也就是说代码中的
console.log(props)
必须只打印出{width: 100, height: 100, other: 404}
- 代码运行处:https://codesandbox.io/s/z6q431v44l
- 选项
-
props
-
rest
-
...props
-
...rest
-
{...props}
-
{...rest}
-
- 答案:https://codesandbox.io/s/cranky-cerf-8jvx1v
代码
|
|
- 需求是点击
button 1
就调用log(1)
,点击button 2
就调用log(2)
,请问_____1_____
处应该怎么填?(只填1处不填2处) - 代码运行处:https://codesandbox.io/s/qqkn530q4w
- 选项
-
log(1)
-
()=>log(1)
-
下面代码的输出结果是什么?(请用大脑推断,不要借助控制台来运行,下同)
|
|
- 选项
- 先输出 ‘error1’ 再输出 ‘error2’
- 先输出 ‘error1’ 再输出 ‘success2’
- 先输出 ‘success1’ 再输出 ‘success2’
- 先输出 ‘success1’ 再输出 ‘error2’
下面代码的输出结果是什么?
|
|
- 选项
- ‘hello’
- timer 的 id
- undefined
下面代码的输出结果是什么?
|
|
- 选项
- ‘MyName’
- ‘NewName’
- undefined
参考文章 ⇧
- React起手式 PDF
- 使用
Vite
搭建react应用 create-react-app
create-react-app
官网create-react-library
create-react-library
中文简介
相关文章 ⇧
- ESM(ECMA Script Modules)
- React18 createRoot
React 18
,不再支持render
绑定根元素节点,否则降版本到react 17
- React 18 用 createRoot 替换 render,保留 Legacy Root API 平滑升级
- React18 为什么要用 createRoot 取代 render
- React18 我帮一朋友重构了点代码,他直呼牛批,但基操勿六
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名