React起手式
大纲链接 §
[toc]
如何入门
React
- 写一个
Hello World,写一个+1,写一个ToDo - 把文档过一遍
- 组件
- 父子组件通信
- 组件生命周期 使用
Hooks API模拟 Context API怎么用Hooks API怎么用useStateuseEffectCSS in JSstyled-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 ⇧
有两种方式
CDNimport
从 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项目
|
|
进入项目 安装依赖 运行项目
|
|
简化运行命令 设置别名
pdforpnpm 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?- 使用 函数作用域 立即执行函数 立即传入参数
ifor(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 + alet 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.jsxconst 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
- 更快速地启动项目,使用
esbuild0打包运行项目 - 详见 使用
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势均力敌
- 都可以写
HTMLVue写在.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-appcreate-react-app官网create-react-librarycreate-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
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名