1,基于rem单位的屏幕等比例缩放
1,什么是rem
rem是浏览器描述长度的单位,含义为:相对于 html
的字体大小的单位。1rem = html 根节点上1个字符的宽度
2,rem的作用
使用 rem
单位设置的元素尺寸,会在不同的设备屏幕宽度下(例如:手机屏幕和平板屏幕)等比例缩放
总结
- 确立参考系,定义标准设备的屏幕宽度和字体大小
- 比例公式(等式左右两边比例尺相同,从而达到等比例缩放的目的):标准屏幕宽度 / 标准字体大小 = 新的屏幕宽度 / 新的屏幕字体大小
- 将页面样式中的
px
单位换算并替换为rem
,方法是?rem = 元素的尺寸 / 标准字体大小
- 绑定窗口的
resize
和load
事件,触发事件时计算出新的屏幕宽度时的字体大小,设置html
的字体大小
2,css预编译工具
预编译工具:sass
sass
工具用于对 css
进行预编译,预编译的css内容,是一个 sass/scss
文件,文件中的语法,大部分和 css
相同,有一部分是预编译的语法。
作用:在 css
的基础上扩展一些实用的功能。
语法
Nesting 嵌套
nav {
// 嵌套 的内容 最终被解释翻译成了 子代选择器
// 这样的话 内部的样式 只有在 nav 的子代才会生效
ul {
margin: 0;
padding: 0;
list-style: none;
}
li { display: inline-block; }
a {
display: block;
padding: 6px 12px;
text-decoration: none;
}
}
使用use引入其他的sass文件
假设有个 base.scss
文件
// base.scss
$color: red;
.content {
font-size: 12px;
}
假设有个 main.scss
文件 在文件中引入 base.scss
且 两个文件在同一目录下
// main.scss
// 使用@use引入外部 scss 文件
@use 'base'; // 该路径是相对路径,相对参考的是当前文件(main.scss)的路径
.box {
background-color: base.$color; // 调用变量是要加上 base 命名空间(namespace)
}
mixin 混合
// 定义函数
// 类似定义一个函数
// 圆括号中是参数列表
@mixin fn($primary-color: #00f, $secondary-color: #ccc) {
color: $primary-color;
background-color: $secondary-color;
width: 200px;
height: 100px;
}
.child1 {
// 不带参数调用函数fn
// 这样的话 child1 将具备 fn 的所有样式
@include fn;
}
.child2 {
// 带参数调用函数fn
// 可以给参数显示的声明参数名
// 多个参数间用逗号隔开
@include fn($secondary-color: #000, $primary-color: #fff);
}
.child3 {
// 带参数调用函数fn
// 类似js调用函数,参数按顺序传入
@include fn(red, green);
}
extend 继承
// 声明父类
%parent {
border: 1px solid black;
font-size: 16px;
font-weight: 200;
font-family: "微软雅黑";
}
// 声明子类
.child1 {
// 通过 @extend 关键字继承父类
@extend %parent;
// 子类可以有自己的属性
// 且 子类属性若和父类属性重复的话 子类属性会覆盖父类的属性
color: green;
border-color: yellow;
font-size: 64px;
}
四则运算
.content {
// sass 中数值可以做四则运算,但是四则运算的两个数字必须单位相同
// 或者 乘除运算可以直接和不带单位的数字进行运算(相当于放大或缩小一定的倍数)
// width: 10ex / 3ex * 10 * 5px;
// height: 15px * 5;
width: 10px + 2 * 5px;
height: ((1px + 1px) / ((50rem/25rem) * 1px)) * 5rem;
transition: (1ms / 12ms * 5s) linear forward;
transform: rotateX(30deg * 3);
}
3,bootstrap
bootstrap当中包括了很多的布局
container布局
<div class="container">
<div class="content">container</div>
</div>
display显示方式
<!-- 当屏幕宽度小于 sm 时显示 -->
<div class="container d-block d-sm-none">小于 sm</div>
<!-- 当屏幕宽度为 md 时 显示 -->
<div class="container d-none d-sm-block d-md-none">md</div>
<!-- 当屏幕宽度大于 lg 时 显示 -->
<div class="container d-none d-md-block">大于 lg</div>
float_position_flex
<div class="clearfix">
<div class="box bg-warning float-start"></div>
<div class="box bg-success float-start"></div>
<div class="box bg-danger float-end"></div>
</div>
gird网格
<div class="container">
<!-- 行 -->
<div class="row">
<!-- 列
col 语法: col-{breakpoints}-{value}
例如: col-sm-3 col-lg-12
value: 范围在 1~12
bootstrap 中 一行 row 被等分为 12 分 那么col的value值代表的是占多少份
例如: col-3 此单元格占 12分之3份
-->
<div class="col">
<div class="bg-primary">1</div>
</div>
<div class="col">
<div class="bg-primary">2</div>
</div>
<div class="col">
<div class="bg-primary">3</div>
</div>
</div>
<div class="row">
<div class="col-8">
<div class="bg-primary">1</div>
</div>
<div class="col">
<div class="bg-primary">2</div>
</div>
<div class="col">
<div class="bg-primary">3</div>
</div>
</div>
</div>
<h1>纵向排列方式</h1>
<div class="container">
<!-- 在 row 上可以使用flex 的 align-items 来进行竖直方向的排列 -->
<div class="row border border-3 align-items-center" style="height: 300px;">
<div class="col">
<div class="bg-success">1</div>
</div>
<div class="col">
<div class="bg-success">2</div>
</div>
<div class="col">
<div class="bg-success">3</div>
</div>
</div>
</div>
<h1>横向排列方式</h1>
<div class="container">
<!-- 在 row 上可以使用 flex 的 justify-content 来进行水平方向的排列 -->
<div class="row border border-3 justify-content-between">
<div class="col-2">
<div class="bg-warning">1</div>
</div>
<div class="col-2">
<div class="bg-warning">2</div>
</div>
<div class="col-2">
<div class="bg-warning">3</div>
</div>
</div>
</div>
<h1>单元格偏移</h1>
<div class="container">
<div class="row border border-3">
<!-- offset 设置单元格左侧的偏移量 数字代表的含义和 col 相同 -->
<div class="col-3 offset-3">
<div class="bg-danger">1</div>
</div>
<div class="col-3 offset-3">
<div class="bg-danger">2</div>
</div>
</div>
</div>
<h1>单元格间距</h1>
<div class="container">
<!-- 添加单元格间距使用 gutter 首字母为 g
可以使用 g-{value} 或 gx-{value} gy-{value}
g-{value} 水平和竖直间距
gx-{value} 水平间距
gy-{value} 竖直间距
-->
<div class="row text-light g-5 border border-3">
<div class="col-4">
<div class="bg-info">1-1</div>
</div>
<div class="col-4">
<div class="bg-info">1-2</div>
</div>
<div class="col-4">
<div class="bg-info">1-3</div>
</div>
<div class="col-4">
<div class="bg-info">2-1</div>
</div>
<div class="col-4">
<div class="bg-info">2-2</div>
</div>
<div class="col-4">
<div class="bg-info">2-3</div>
</div>
</div>
</div>
<h1>可以指定每一行显示多少列</h1>
<div class="container">
<!-- row-cols-{breakpoints}-{value} 让一行显示多少列 -->
<div class="text-light row row-cols-1 row-cols-md-2 row-cols-lg-3">
<div class="col">
<div class="bg-dark">1</div>
</div>
<div class="col">
<div class="bg-dark">2</div>
</div>
<div class="col">
<div class="bg-dark">3</div>
</div>
<div class="col">
<div class="bg-dark">4</div>
</div>
<div class="col">
<div class="bg-dark">5</div>
</div>
<div class="col">
<div class="bg-dark">6</div>
</div>
<div class="col">
<div class="bg-dark">7</div>
</div>
<div class="col">
<div class="bg-dark">8</div>
</div>
<div class="col">
<div class="bg-dark">9</div>
</div>
<div class="col">
<div class="bg-dark">10</div>
</div>
<div class="col">
<div class="bg-dark">11</div>
</div>
<div class="col">
<div class="bg-dark">12</div>
</div>
</div>
</div>
color
<h1>背景色</h1>
<!-- bg-{value} -->
<div class="bg-primary">1</div>
<div class="bg-secondary">1</div>
<div class="bg-success">1</div>
<div class="bg-danger">1</div>
<div class="bg-warning">1</div>
<div class="bg-info">1</div>
<div class="bg-light">1</div>
<div class="bg-dark">1</div>
<h1>文本色</h1>
<!-- text-{value} -->
<div class="text-primary">hello world</div>
<div class="text-secondary">hello world</div>
<div class="text-success">hello world</div>
<div class="text-danger">hello world</div>
<div class="text-warning">hello world</div>
<div class="text-info">hello world</div>
<div class="text-light">hello world</div>
<div class="text-dark">hello world</div>
<h1>文本+背景色</h1>
<!-- text-bg-{value} -->
<div class="text-bg-primary">hello world</div>
<div class="text-bg-secondary">hello world</div>
<div class="text-bg-success">hello world</div>
<div class="text-bg-danger">hello world</div>
<div class="text-bg-warning">hello world</div>
<div class="text-bg-info">hello world</div>
<div class="text-bg-light">hello world</div>
<div class="text-bg-dark">hello world</div>
size_space_stack
<h1>元素大小</h1>
<div class="text-bg-primary w-25">25%</div>
<div class="text-bg-secondary w-50">50%</div>
<div class="text-bg-success w-75">75%</div>
<div class="text-bg-warning w-100">100%</div>
<div class="text-bg-danger w-auto">auto</div>
<div class="container d-flex" style="height:400px;">
<div class="text-bg-primary h-25">25%</div>
<div class="text-bg-secondary h-50">50%</div>
<div class="text-bg-success h-75">75%</div>
<div class="text-bg-warning h-100">100%</div>
<div class="text-bg-danger h-auto">auto</div>
</div>
text
<!-- 对齐方式 -->
<div class="border border-5 text-end">hello world</div>
<!-- 换行和溢出 -->
<div style="width: 300px" class="border border-5 text-nowrap overflow-scroll">
hello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello
worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello world
</div>
<!-- 字体大小 -->
<div class="fs-1">hello world</div>
<!-- 粗细和加斜 fst-italic fst-normal -->
<div class="fw-bolder">hello world</div>
<div class="fst-italic">hello world</div>
<!-- 行高
lh-{value}
value: 取值范围 1 sm base lg
-->
<div style="width: 300px" class="border border-5 lh-1">
hello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello
world
</div>
<!-- 文本装饰线 -->
<div class="d-inline text-decoration-underline">
hello world
</div>
<div class="d-inline text-decoration-line-through">
hello world
</div>
<a class="text-decoration-none" href="#">hello world</a>
form-control
<div class="card p-3" style="width: 300px;">
<div class="vstack gap-2">
<div>
<label class="form-label">姓名</label>
<input class="form-control" type="text" />
</div>
<div>
<label class="form-label">年龄</label>
<!-- 设置大小 -->
<input class="form-control form-control-sm" type="number" />
<input class="form-control form-control-lg" type="number" />
<!-- 禁用 disabled -->
<input class="form-control" type="number" disabled />
<!-- 只读 readonly -->
<input class="readonly form-control" type="number" readonly />
</div>
<div>
<label class="form-label">邮箱</label>
<!-- 朴素输入框 form-control-plaintext -->
<input class="form-control form-control-plaintext" type="text" readonly value="xxx@xxx.com" />
</div>
<div>
<label class="form-label">简介</label>
<!-- form-control 相关样式都可以用在文本域上 -->
<textarea disabled rows="4" class="form-control" type="text"></textarea>
</div>
</div>
</div>
<!-- label 与 表单元素 左右排列 -->
<div class="card p-3" style="width: 500px">
<div class="row">
<label class="col-3 col-form-label">姓名</label>
<div class="col">
<input class="form-control" type="text">
</div>
</div>
</div>
原生表单验证
<body class="d-flex justify-content-center">
<!-- novalidate 屏蔽默认的表单提交时的验证报告
屏蔽掉自动的验证报告的目的 是为了我们自己好去控制验证报告
-->
<form class="card p-3" style="width: 500px;" onsubmit="return false" novalidate>
<div class="vstack gap-3">
<div class="row">
<label class="col-3 col-form-label">姓名</label>
<div class="col">
<!-- required 必填 -->
<!-- <input name="name" required type="text" class="form-control"> -->
<!-- pattern 正则表达式 -->
<!-- <input name="name" pattern="[\s\S]*张三[\s\S]*" type="text" class="form-control"> -->
<!-- minlength 最小长度 maxlength 最大长度 -->
<!-- <input name="name" minlength="2" maxlength="10" type="text" class="form-control"> -->
<!-- min 最小值 max 最大值 -->
<input name="name" min="0" max="200" type="number" class="form-control">
<!-- type 类型验证 -->
<!-- <input type="email" class="form-control"> -->
</div>
</div>
<div class="row">
<div class="col-3"></div>
<div class="col">
<button class="btn btn-primary w-100">提交</button>
</div>
</div>
</div>
</form>
<!-- 1. 添加 novalidate -->
<form class="form2 card p-3" style="width: 500px;" onsubmit="return false" novalidate>
<div class="vstack gap-3">
<div class="row">
<label class="col-3 col-form-label">姓名</label>
<div class="col">
<input required minlength="2" maxlength="10" name="name" type="text" class="form-control">
</div>
</div>
<div class="row">
<label class="col-3 col-form-label">年龄</label>
<div class="col">
<input required min="0" max="150" name="age" type="number" class="form-control">
</div>
</div>
<div class="row">
<div class="col-3"></div>
<div class="col">
<button class="btn2 btn btn-primary w-100">提交</button>
</div>
</div>
</div>
</form>
</body>
<script>
let btn = document.querySelector('button')
let form = document.querySelector('form')
let nameInp = document.querySelector('input[name=name]')
let form2 = document.querySelector('.form2')
let btn2 = document.querySelector('.btn2')
let nameInp2 = document.querySelector('.form2 input[name=name]')
let ageInp = document.querySelector('.form2 input[name=age]')
btn.addEventListener('click', () => {
// 可以在此处通过代码验证用户的输入
// 可以通过输入框的 validity 属性 来判断用户是否输入正确
// validity.valueMissing -> required 用户没填数据时为 true
// validity.patternMismatch -> pattern 用户输入不满足正则 true
// validity.rangeOverflow -> max 用户输入超过最大值 true
// validity.rangeUnderflow -> min 用户输入小于最小值 true
// validity.tooLong -> maxlength 用户输入超出长度 不会触发 true
// validity.tooShort -> minlength 用户输入小于了指定长度 true
// validity.valid -> 输入没有问题 验证通过 true
// console.log(nameInp.validity);
// if(nameInp.validity.tooShort) {
// console.log('姓名长度不够');
// }
if (nameInp.validity.rangeOverflow) {
// 手动设置错误提示
nameInp.setCustomValidity('年龄不能大于200')
} else if (nameInp.validity.rangeUnderflow) {
nameInp.setCustomValidity('年龄不能小于0')
} else {
console.log(nameInp.validity);
// 若验证没有问题时应当设置为 空字符串
nameInp.setCustomValidity('')
}
// checkValidity 检测是否表单输入有效
// 若返回true 代表输入有效 否则为false
if (!form.checkValidity()) {
// 显示验证报告
form.reportValidity()
} else {
// 若表单没有问题 输入都有效 则可以执行后续的代码
// 后续代码写在此处 例如 发送网络请求
console.log('验证通过');
}
})
// 总结表单验证
btn2.addEventListener('click', () => {
// 2. 验证所有表单项
if (nameInp2.validity.valueMissing) {
// 3. 设置错误信息
nameInp2.setCustomValidity('请输入姓名')
} else if (nameInp2.validity.tooShort) {
nameInp2.setCustomValidity('请输入至少2个字的姓名')
} else {
nameInp2.setCustomValidity('')
}
if (ageInp.validity.valueMissing) {
ageInp.setCustomValidity('请输入年龄')
} else if (ageInp.validity.rangeOverflow) {
ageInp.setCustomValidity('请输入不超过150的年龄')
} else if (ageInp.validity.rangeUnderflow) {
ageInp.setCustomValidity('请输入不低于0的年龄')
} else {
ageInp.setCustomValidity('')
}
if (!form2.checkValidity()) {
// 4. 发起验证报告
form2.reportValidity()
} else {
// 5. 验证通过 执行后续逻辑
console.log('验证通过');
}
})
</script>
bootstrap自动表单验证
<body class="d-flex justify-content-center">
<!-- 给form添加类名 was-validated 就可以开启验证效果 -->
<form class="card p-3" style="width: 500px" onsubmit="return false" novalidate>
<div class="vstack gap-3">
<div>
<label class="form-label">姓名</label>
<input required type="text" class="form-control">
<!-- 验证的提示需要写在被验证的输入框下面 -->
<!-- valid-feedback 验证通过的提示文本 -->
<div class="valid-feedback">
ok
</div>
<div class="invalid-feedback">
请输入姓名
</div>
</div>
<div>
<label class="form-label">年龄</label>
<input min="0" max="150" type="number" class="form-control">
<div class="valid-feedback">
ok
</div>
<div class="invalid-feedback">
请输入0~150的年龄
</div>
</div>
<div>
<button class="btn btn-primary w-100">提交</button>
</div>
</div>
</form>
</body>
<script>
let btn = document.querySelector('button')
let form = document.querySelector('form')
btn.addEventListener('click', () => {
form.classList.add('was-validated')
})
</script>
bootstrap手动表单验证
<body class="d-flex justify-content-center">
<form class="card p-3" style="width: 500px" onsubmit="return false" novalidate>
<div class="vstack gap-3">
<div class="row">
<label class="col-3 col-form-label">姓名</label>
<div class="col">
<input name="name" type="text" class="form-control">
<div class="valid-feedback">ok</div>
<div class="invalid-feedback">error</div>
</div>
</div>
<div class="row">
<label class="col-3 col-form-label">年龄</label>
<div class="col">
<input name="age" type="number" class="form-control">
<div class="valid-feedback">ok</div>
<div class="invalid-feedback">error</div>
</div>
</div>
<div class="row">
<div class="col-3"></div>
<div class="col"><button class="btn btn-primary w-100">提交</button></div>
</div>
</div>
</form>
</body>
<script>
let nameInp = document.querySelector('input[name=name]')
let ageInp = document.querySelector('input[name=age]')
// 查询错误提示的元素
let nameErrTip = document.querySelector('input[name=name]~.invalid-feedback')
let ageErrTip = document.querySelector('input[name=age]~.invalid-feedback')
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
// 手动通过代码进行验证
// 清空所有的 is-valid 和 is-invalid
nameInp.classList.remove('is-valid', 'is-invalid')
ageInp.classList.remove('is-valid', 'is-invalid')
// 定义一个代表验证通过的变量
let nameValid = true
if (nameInp.value.trim() === '') {
nameErrTip.textContent = '请输入姓名'
nameValid = false
} else if (nameInp.value.trim().length < 2 || nameInp.value.trim().length > 10) {
nameErrTip.textContent = '请输入2~10个字的姓名'
nameValid = false
}
let ageValid = true
if (Number(ageInp.value) < 0 || Number(ageInp.value) > 150) {
ageErrTip.textContent = '请输入0~150之间的年龄'
ageValid = false
}
nameInp.classList.add(nameValid ? 'is-valid' : 'is-invalid')
ageInp.classList.add(ageValid ? 'is-valid' : 'is-invalid')
if (nameValid && ageValid) {
// 整个表单输入正确的情况
// 可以执行后续的网络请求
console.log('验证通过');
}
})
</script>
4,react
jsx语法
<body>
<div id="app"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector('#app'));
// 使用jsx创建一个react-dom对象
// react-dom 是 react 封装的类似 dom 对象的对象
// 作用: 用于描述页面元素
const h1 = <h1>hello world</h1>
// jsx 使用 圆括号 声明多行的 react-dom 对象
const box = (
<div className="box">
<span>hello box</span>
</div>
)
const element = (
<div>
root1
</div>
// react-dom 中只能有一个根节点
// <div>
// root2
// </div>
)
let now = new Date()
// 插值
const chazhi = (
<div>
{ /*使用花括号进行插值 插值的内容可以是js表达式*/}
{'当前时间: ' + now.toLocaleString()}
</div>
)
let id = 'thisIsMyId'
// 定义样式对象
let myStyle = {
width: '100px',
height: '100px',
backgroundColor: '#f00'
}
// 插值 html 属性
const htmlAttr = (
<div>
{/* 插值id */}
<div id={id}>
hello attr
</div>
{/* 插值 style */}
{/* style 属性无法直接赋值一个字符串如下: */}
{/*<div style="width: 100px; height: 100px; background-color: #f00"></div>*/}
<div style={myStyle}></div>
{/* 插值 class */}
<div className="box active"></div>
</div>
)
// 使用函数创建react-dom对象
const fnDom = React.createElement(
// 标签名
'div',
// 标签上的属性
{id: 'myFunctionDom', className: 'my-dom', style: {width: '500px', height: '50px', backgroundColor: '#00f'}},
// 子元素数组 或 标签体字符串
[
// 此处的第三个参数就是一个普通的字符串充当 span 标签的标签体
React.createElement('span', {key: '1'}, 'hello world'),
// 若此处 createElement 第三个参数是一个数组的话,需要给元素添加 key 属性
// key 属性是一个唯一值 不重复即可
React.createElement('span', {key: '2'}, 'hello world')
]
)
// root.render 函数可以渲染一个 react-dom 对象
root.render((
<div className="container">
{/* 插值的内容 若是 react-dom 对象 那么就会被页面显示出来 */}
{h1}
{box}
{element}
{chazhi}
{htmlAttr}
{fnDom}
</div>
))
</script>
元素渲染
<script type="text/babel">
let root = ReactDOM.createRoot(document.querySelector('#root'))
// 初始化渲染
let now = new Date()
root.render((
<div>
<div className="now">当前时间:</div>
<div className="time">
{now.toLocaleString()}
</div>
</div>
))
// 渲染循环
let timer = setInterval(() => {
// 修改渲染逻辑
now = new Date()
// 渲染内容
root.render((
<div>
<div className="now">当前时间:</div>
{/* react 在每次更新的时候,都会去对比每一个 react-dom 节点
只有发现该被对比的节点有变化时(标签体变化 子节点数量变化 节点元素变化等) 才会更新节点
*/}
<div className="time">
{now.toLocaleString()}
</div>
</div>
))
}, 1000)
</script>
条件渲染
<body>
<div id="root"></div>
</body>
<script type="text/babel">
let sex = 'male'
ReactDOM.createRoot(document.querySelector('#root')).render((
<div>
{/* 使用 && 进行短路运算 前一个表达式为true时 就显示后面表达式的内容 */}
{sex === 'male' && <div style={{color: '#00f'}}>男</div>}
{sex === 'female' && <div style={{color: 'pink'}}>女</div>}
{sex === 'other' && <div style={{color: '#ff0'}}>其他</div>}
{/* 使用三元运算符 按条件显示不同的内容 */}
{sex === 'male' ? <div style={{color: '#00f'}}>男</div> :
sex === 'female' ? <div style={{color: 'pink'}}>女</div> :
<div style={{color: '#ff0'}}>其他</div>
}
</div>
))
</script>
循环渲染
<body>
<div id="root"></div>
</body>
<script type="text/babel">
let students = [
{
name: '张三',
sex: 'male',
age: 17
},
{
name: '李四',
sex: 'female',
age: 24
},
{
name: '隔壁老王',
sex: 'other',
age: 30
},
]
ReactDOM.createRoot(document.querySelector('#root')).render((
<div>
<ul>
{/* 循环渲染,使用一个数组的map函数 返回一个由 react-dom 充当成员形成的一个新数组
循环渲染一定要添加 key
*/}
{students.map((item,index) => <li key={index}>姓名: {item.name}; 性别: {item.sex === 'male' ? '男' :
item.sex === 'female' ? '女' : '不详'
}; 年龄: {item.age}</li>)}
</ul>
</div>
))
</script>
受控组件
<div id="root"></div>
</body>
<script type="text/babel">
// 知识点
// 什么是受控组件?
// 被 react 的 state 控制显示和输入的表单元素称为受控组件
// 受控组件的数据来自 state 而不是表单元素自身
// react 中 所有的事件都不能通过 return false 来屏蔽默认事件
// 声明 input[type=text] 、select 和 textarea 的受控组件
// 声明 input[type=radio] input[type=checkbox] 的受控组件
function App() {
const [name, setName] = React.useState('')
const [clazz, setClazz] = React.useState('')
const [desc, setDesc] = React.useState('')
const [sex, setSex] = React.useState('other')
const [hobbies, setHobbies] = React.useState(['dlq', 'ymq'])
function submit(ev) {
ev.preventDefault()
console.log(name)
console.log(clazz)
console.log(desc)
console.log(sex)
console.log(hobbies)
}
function onNameInput(ev) {
console.log(ev)
console.log(ev.target.value)
// 由于此处的输入框是受控组件
// 所以若不改变状态, name 值不发生改变,页面就不会更新
setName(ev.target.value)
}
function onClazzChange(ev) {
console.log(ev.target.value)
setClazz(ev.target.value)
}
function onDescInput(ev) {
console.log(ev.target.value)
setDesc(ev.target.value)
}
function onSexChange(ev) {
console.log(ev.target.value)
setSex(ev.target.value)
}
function onHobbiesChange(ev) {
console.log(ev.target.value)
if (hobbies.includes(ev.target.value)) {
// 若已经包含就了删除
setHobbies(_hobbies => {
let i = _hobbies.findIndex(item => item === ev.target.value)
_hobbies.splice(i, 1)
return [..._hobbies]
})
} else {
// 不存在时 就添加进数组
setHobbies(_hobbies => {
_hobbies.push(ev.target.value)
return [..._hobbies]
})
}
}
return (
<div>
<form>
<div>
<label>
姓名:
<input type="text" value={name} onInput={onNameInput}/>
</label>
</div>
<div>
<label>
班级:
<select value={clazz} onChange={onClazzChange}>
<option value="" disabled>请选择</option>
<option value="1">一班</option>
<option value="2">二班</option>
<option value="3">三班</option>
</select>
</label>
</div>
<div>
<label>
简介:
<textarea rows="4" value={desc} onInput={onDescInput}></textarea>
</label>
</div>
<div>
<label>
性别:
<label><input onChange={onSexChange} checked={sex === 'male'} type="radio" value="male"/>男</label>
<label><input onChange={onSexChange} checked={sex === 'female'} type="radio"
value="female"/>女</label>
<label><input onChange={onSexChange} checked={sex === 'other'} type="radio" value="other"/>其他</label>
</label>
</div>
<div>
<label>
爱好:
<label><input onChange={onHobbiesChange} checked={hobbies.includes('dlq')} type="checkbox"
value="dlq"/>打篮球</label>
<label><input onChange={onHobbiesChange} checked={hobbies.includes('tzq')} type="checkbox"
value="tzq"/>踢足球</label>
<label><input onChange={onHobbiesChange} checked={hobbies.includes('ymq')} type="checkbox"
value="ymq"/>羽毛球</label>
</label>
</div>
<div>
<button onClick={submit}>提交</button>
</div>
</form>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
状态提升
<div id="root"></div>
</body>
<script type="text/babel">
// 文档:https://zh-hans.reactjs.org/docs/lifting-state-up.html
// 知识点
// 什么是状态提升?
// 组件向上级组件汇报自己状态的过程叫做状态提升
// 应用场景
// 1. 在父组件中需要读取子组件状态时,可以让子组件状态提升给父组件
// 2. 子组件需要发出某个事件,且事件将携带参数时
// 如何实现?
// 理论原理:
// 组件可以利用自身的 props 属性将自身状态提升到上级组件
// 表现形式类似于触发事件
// 上级组件只要绑定事件(本质是提供一个回调函数)接收参数即可
// 操作方法:
// 1. 父组件中,给子组件添加 props 属性,并分配一个函数值,该函数接收子组件的状态值为参数
// 2. 子组件中,在适当时机调用父组件提供的 props,并将自身状态作为参数传入父组件提供的函数
function Child(props) {
const [count, setCount] = React.useState(0);
function increase() {
setCount(_c => _c + 1)
}
// 监视 count 的变化
React.useEffect(() => {
// count 发生变化
// 通知父组件
// 将状态值作为参数传入
props.countChange(count)
}, [count])
return (
<div>
<div>count: {count}</div>
<div>
<button onClick={increase}>+</button>
</div>
</div>
)
}
function App() {
// 此函数的参数用于接收子组件状态
function onCountChange(args) {
console.log(args)
}
return (
<div>
{/* 给子组件分配一个函数 用于接收组件状态 */}
<Child countChange={onCountChange}></Child>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
组件通信
<body>
<div id="root"></div>
</body>
<script type="text/babel">
// 知识点
// 什么是组件通信
// 父组件和子组件相互传递参数的过程,就是组件间的通信
// 应用场景
// 子组件的状态依赖于父组件传入的 props
// 由于 props 是只读的
// 所以要修改 props 子组件只能通知父组件更新数据
// 这个过程就会用到组件间的通信方法
// 通信方法:
// 1. 父组件将自身 state 作为参数传入子组件 props
// 2. 子组件依赖 props 显示内容
// 3. 子组件内希望修改 props 中的值,则发出一个事件
// 4. 父组件绑定子组件发出的事件并接收参数
// 5. 父组件接收事件后更新自身 state,此时 react 会自动更新子组件 props
class MyTr extends React.Component {
onNameInput(ev) {
console.log(ev.target.value)
// 若想修改 this.props.name
// 那么只能通过通知父组件,让父组件修改
// this.props.onNameInput(this.props.listId, ev.target.value)
console.log(this)
}
render() {
return (
<tr>
<td>{this.props.listId}</td>
<td><input type="text" value={this.props.name} onInput={this.onNameInput.bind(this)}/></td>
<td>{this.props.sex}</td>
<td>{this.props.age}</td>
<td>
<button>删除</button>
</td>
</tr>
)
}
}
function App() {
const [list, setList] = React.useState([
{
id: 0,
name: '张三',
sex: 'male',
age: 17
},
{
id: 1,
name: '李四',
sex: 'female',
age: 24
},
{
id: 2,
name: '老王',
sex: 'other',
age: 30
},
]);
// 父组件接收子组件的状态提升
function onNameInput(listId, newValue) {
console.log(listId, newValue)
// 修改父组件的状态
// 从而更新子组件
setList(_list => {
// 查找id相同的对象索引
let i = _list.findIndex(item => item.id === listId)
// 赋值新的值
_list[i].name = newValue
return [..._list]
})
}
return (
<div>
<table border="true">
<thead>
<tr>
<th>id</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{list.map(item => <MyTr onNameInput={onNameInput} key={item.id} listId={item.id} name={item.name}
sex={item.sex}
age={item.age}></MyTr>)}
</tbody>
</table>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
错误边界(异常处理)
<body>
<div id="root"></div>
</body>
<script type="text/babel">
// 知识点
// 什么是错误边界
// react 的错误边界类似 js 的 catch
// 错误边界是一个 react 组件,其内部的子组件一旦发生异常就会触发错误边界
// 应用场景
// 需要对页面异常做出更友好的提示的时候,可以使用错误边界
// 如何使用?
// 1. 错误边界需要是一个 class 组件(函数组件不能充当错误边界)
// 2. 错误边界需要声明一个 hasError 状态
// 3. 错误边界需要实现生命周期钩子函数: static getDerivedStateFromError(error) 该函数返回要修改的状态,通常是{hasError: true} ; componentDidCatch(error, errorInfo) [该函数非必要]
// 4. 错误边界的渲染函数的内容,根据 hasError 状态进行调整
// 5. 使用错误边界的时候,在错误边界的标签体中嵌入其他子组件
// 6. 子组件生命周期任何阶段发生异常,都会被错误边界捕获到,从而显示错误的提示
// 使用 类组件声明错误边界
class ErrorBoundary extends React.Component {
state = {
// hasError 状态值用于指示 错误边界 内的子组件是否有错误
hasError: false
}
// 当组件发生异常时,在调用 render 函数前先会调用此函数
// error 当前正被触发的异常对象
static getDerivedStateFromError(error) {
console.log('getDerivedStateFromError')
console.log(error)
// 此处在渲染之前修改 hasError 状态为 true 代表产生了异常
return {hasError: true}
}
// 此为非必填函数
// 该生命周期将在捕获异常完成并渲染完成后触发
componentDidCatch(error, errorInfo) {
console.log('componentDidCatch')
console.log(error)
console.log(errorInfo)
}
render() {
// render 函数通过 hasError 判断应该显示什么内容
// 没有异常就显示 ErrorBoundary 的标签体里的内容 否则显示一个友好的错误提示
return this.state.hasError ? <div>错误时候看到的内容</div> :
<div>{this.props.children}</div>
}
}
class Child extends React.Component {
render() {
// 子组件中任意位置出现异常 则会被错误边界捕获到
abc.ok()
return (
<div>
this is child
</div>
)
}
}
function App() {
return (
<div>
{/* 使用错误边界包裹正常显示的内容,此处的显示内容为 Child组件
那么当 Child 组件抛出异常时 则 ErrorBoundary 就显示友好的提示
*/}
<ErrorBoundary>
<Child></Child>
</ErrorBoundary>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
元素分组Fragments
<body>
<div id="root"></div>
</body>
<script type="text/babel">
function App() {
const [list, setList] = React.useState([
{
name: '张三',
sex: 'male'
},
{
name: '李四',
sex: 'female'
},
]);
return (
<div>
{/* React.Fragment 内置组件起到一个给元素分组的作用,最终不会显示到页面上
作用类似小程序中的 <block> 标签
*/}
{list.map((item, i) => (
<React.Fragment key={i}>
<div>{item.name}</div>
<div>{item.sex}</div>
</React.Fragment>
))}
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
Context上下文
<body>
<div id="root"></div>
</body>
<script type="text/babel">
// 文档:https://zh-hans.reactjs.org/docs/context.html
// 知识点
// 什么是 context?
// context 是一对 组件,用于向后代组件提供参数
// 注意: context 的数据是只读的
// 应用场景
// 在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的
// 但此种用法对于某些类型的属性而言是极其繁琐的
// (例如:地区偏好,UI 主题,因为他们是全局属性,每个和ui相关组件都应该读取其值)
// 所以:很多不同层级的组件需要访问一些同样的数据,可以考虑使用 context
// 概念
// context 提供了一对组件 如下:
// Provider(供应商) 提供数据的组件
// Consumer(消费者) 消费数据的组件
// 使用方法
// 1. 创建 context
// 语法:const MyContext = React.createContext(defaultValue);
// defaultValue 若组件找不到对应 context 时会获取默认值
// context 的值可以是一个对象
// 2. 页面中将 MyContext.Provider 作为父节点使用,并传入 value 参数作为 Provider 提供的数据
// 3. 子组件接收 Provider 提供的数据
// 3.1 类组件接收方法:
// 1. 给类名的 contextType 赋值 context 对象,例如:
// MyClassComponent.contextType = MyContext
// 2. 在组件内使用 this.context 访问 Provider 提供的数据
// 3.2 函数组件接收方法:
// 1. 在页面中,给要使用数据的函数组件套上一个 Consumer 父组件
// 2. 在 Consumer 内 通过工厂函数返回要接收 Provider 数据的函数组件,工厂函数接收一个 value 参数,该参数即为 Provider 提供的数据
// 3. 函数组件通过 props 接收 value 参数
// 例如:
// <MyContext.Consumer>
// {value => <FunctionComponent theme={value}></FunctionComponent>}
// </MyContext.Consumer>
// Provider 提供一个动态可变的值
// 绑定多个 Context
// 官网:https://zh-hans.reactjs.org/docs/context.html#consuming-multiple-contexts
// 重点在于利用多重嵌套的 Context.Consumer,来实现绑定多个 Context
// 创建 context 上下文
// 默认值什么时候会被使用?
// 读取上下文的组件不在上下文中时 会用到默认值
const ThemeContext = React.createContext('dark');
class ClazzComponent extends React.Component {
render() {
return (
// 类组件中可以使用 this.context 来访问 Provider 提供的 value
<div style={{
height: '100px',
backgroundColor: this.context === 'light' ? '#ff0' : '#000',
color: this.context === 'light' ? '#000' : '#fff'
}}>
<h1>类组件</h1>
</div>
)
}
}
function FnComponent(props) {
return (
// 函数组件通过 props 读取 context 赋值的内容
<div style={{
height: '100px',
backgroundColor: props.theme === 'light' ? '#ff0' : '#000',
color: props.theme === 'light' ? '#000' : '#fff'
}}>
<h1>函数组件</h1>
</div>
)
}
// 给组件指定对应的上下文对象
ClazzComponent.contextType = ThemeContext
function App() {
// 保存主题的状态
const [theme, setTheme] = React.useState('light');
function changeTheme() {
setTheme(_t => {
return _t === 'light' ? 'dark' : 'light'
})
}
return (
<div>
{/* 若组件在 Provider 的外部 则context将使用默认值 */}
<ClazzComponent></ClazzComponent>
<button onClick={changeTheme}>{theme}</button>
{/* 声明供应商 value 属性就是供应商要提供给消费者的数据 */}
<ThemeContext.Provider value={theme}>
<ClazzComponent></ClazzComponent>
<ThemeContext.Consumer>
{/* 函数组件使用 Consumer 进行包裹
要使用一个工厂函数返回一个函数组件
工厂函数的参数value就代表着 Provider 提供的value
通过函数组件的props将value设置进去
*/}
{value => <FnComponent theme={value}></FnComponent>}
</ThemeContext.Consumer>
</ThemeContext.Provider>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
refs转发
<body>
<div id="root"></div>
</body>
<script type="text/babel">
// 文档:https://zh-hans.reactjs.org/docs/forwarding-refs.html
// https://zh-hans.reactjs.org/docs/refs-and-the-dom.html
// 知识点
// 什么是转发?
// 转发(ref) 的作用是获取组件中的 dom 或 一个组件
// 应用场景
// 1. 在 react 项目中 若希望通过 document.querySelector 获取某个元素时,我们应该使用 ref 替代
// 2. 当需要获取某个子组件的状态或调用组件的方法的时候
// 注意:函数组件上 不能添加 ref
// 使用方法:
// 类组件使用 React.createRef()
// 函数组件使用 React.useRef()
// 通用方法 使用箭头函数 el => { temp = el }
// React.forwardRef
class ClazzComponent extends React.Component {
// 创建一个 ref 对象 并保存到组件属性中
inpRef = React.createRef()
inpEl
// 声明属性保存子组件
child
child2
onClick() {
// 可以通过 this.inpRef.current 来获取 input 元素
// console.log(this.inpRef.current.value)
console.log(this.inpEl.value)
}
onClick2() {
// 获取子组件
console.log(this.child)
// 调用子组件的属性
console.log(this.child.state)
// 调用子组件的方法
this.child.clear()
console.log(this.child2.value)
}
render() {
return (
<div>
<h1>类组件</h1>
{/* 给元素的 ref 属性 赋值一个 ref 对象 */}
{/*<input ref={this.inpRef} type="text"/>*/}
{/* 此处 el 代表的就是当前元素 input */}
<input ref={el => {
this.inpEl = el
}} type="text"/>
<button onClick={this.onClick.bind(this)}>读取输入框的值</button>
{/* 注意: ref 属性可以写在 类组件上 但不可以写在函数组件上 */}
<Child ref={child => {
this.child = child
}}></Child>
<Child2 ref={child2 => {
this.child2 = child2
}}></Child2>
<button onClick={this.onClick2.bind(this)}>清空child的输入框</button>
</div>
)
}
}
function FnComponent() {
// 函数组件内使用 useRef 来创建ref 对象
const inpRef = React.useRef()
const [inpEl, setInpEl] = React.useState(null)
function onClick() {
// 此处的 inpRef 和类组件相同
// 可以使用 inpRef.current 读取元素
// console.log(inpRef.current.value)
console.log(inpEl.value)
}
return (
<div>
<h1>函数组件</h1>
{/*<input ref={inpRef} type="text"/>*/}
<input ref={el => {
setInpEl(el)
}} type="text"/>
<button onClick={onClick}>读取输入框的值</button>
</div>
)
}
class Child extends React.Component {
state = {
value: ''
}
// 清空输入框
clear() {
this.setState({value: ''})
}
render() {
return (
<div>
<h3>child</h3>
<input value={this.state.value} onInput={ev => {
this.setState({value: ev.target.value})
}} type="text"/>
</div>
)
}
}
// 函数组件上无法使用 ref
// function Child2() {
// return (
// <div>child2</div>
// )
// }
// 我们可以使用 React.forwardRef 来创建一个函数组件
// React.forwardRef 创建的函数组件就可以使用ref
// props: 函数组件的 props
// ref: ref对象
// 返回值: 函数组件
const Child2 = React.forwardRef((props, ref) => {
// 此处的内容就是函数组件的内容
const [value, setValue] = React.useState('');
return (
<div>
<h3>child2</h3>
<input ref={ref} value={value} onInput={ev => {
setValue(ev.target.value)
}} type="text"/>
</div>
)
})
function App() {
return (
<div>
<ClazzComponent></ClazzComponent>
<FnComponent></FnComponent>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
Portals传送门
<body>
<div id="other"></div>
<div id="root"></div>
</body>
<script type="text/babel">
// 知识点
// 什么是传送门
// 传送门是 react 通过 ReactDOM.createPortal 方法创建的一个特殊的 react-dom
// 传送门的内容可以显示到 html 文档的任何位置 甚至是 react 根节点外面
// 应用场景
// 制作模态等脱离自身组件结构的内容
// 注意:传送门的使用必须配合无状态的函数组件,class组件是无法使用的
// 使用方法:
// 1. 创建一个函数组件充当传送门组件(该函数组件不能有状态)
// 2. 函数组件内使用 ReactDOM.createProtal 方法创建传送门
// ReactDOM.createPortal 函数将返回一个可以被渲染到页面的一组html元素
// 第一个参数:要传送的 react-dom
// 第二个参数:传送门的目标节点,最后元素将渲染到该节点
// 传送门组件
// 传送门组件必须是函数组件 且不能包含状态
function Portal(props) {
// 使用 props.show 属性来指示传送门是否显示
return props.show ? ReactDOM.createPortal((
<div>
<h3>这是传送们的内容</h3>
<div>我是传送门</div>
</div>
), document.querySelector('#other')) : null
}
function App() {
// 声明一个传送门当前显示状态
const [show, setShow] = React.useState(false);
// 开关函数
function switchPortal() {
setShow(_s => !_s)
}
return (
<div>
<h1>App</h1>
<button onClick={switchPortal}>开关传送门</button>
{/* 插入一个传送门组件 */}
<Portal show={show}></Portal>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#root')).render(<App/>)
</script>
Pofiler检测器
<body>
<div id="app"></div>
</body>
<script type="text/babel">
// Profiler 探测器
// 探测器是一个用于优化页面检测页面运行效率的工具,在生产环境下禁用,因为他会带来额外的开销
function MyDate() {
const [date, setDate] = React.useState(new Date())
React.useEffect(() => {
setInterval(() => {
setDate(new Date())
}, 1000)
return () => {
}
}, [])
return (
<React.Fragment>
{date.toLocaleString()}
</React.Fragment>
)
}
function App() {
return (
<div>
<MyDate/>
</div>
)
}
ReactDOM.createRoot(document.querySelector('#app')).render((
<React.Profiler id="App" onRender={appCallback}>
<App/>
</React.Profiler>
))
function appCallback(
id, // the "id" prop of the Profiler tree that has just committed
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render the entire subtree without memoization
startTime, // when React began rendering this update
commitTime, // when React committed this update
interactions // the Set of interactions belonging to this update
) {
console.log(id)
console.log(phase)
console.log(actualDuration)
console.log(baseDuration)
console.log(startTime)
console.log(commitTime)
console.log(interactions)
}
</script>
5,懒加载
什么是懒加载?
懒加载就是页面中看不到时,就不去加载它,当页面中出现该内容时再去加载
懒加载多用于图片和媒体文件
好处:
好处在于用户看不见的东西就不用使用浏览器去下载了
还可以让页面加载更快
需要使用的东西
- 给需要懒加载的元素添加 data-src
- 给滚动元素添加 scroll 事件监听器
- 计算显示图片的临界值 scrollTop = 内容高 - 图片高 - 窗口高
- 判断容器元素的 scrollTop 大于临界值 加载 data-src
举例:
<body>
<div class="container">
<div class="content">
<img class="img" height="200" src="./img/avatar-max-img.png" data-src="./img/1.png" alt="">
</div>
</div>
</body>
<script>
const container = document.querySelector('.container')
const content = document.querySelector('.content')
const img = document.querySelector('.img')
// 绑定滚动事件
container.addEventListener('scroll', ev => {
// console.log(ev);
console.log('scrollTop1', ev.target.scrollTop);
// 计算显示图片的临界值
// scrollTop = 内容高 - 图片高 - 窗口高
let scrollTop = content.clientHeight - 200 - innerHeight
console.log('scrollTop2', scrollTop);
if (ev.target.scrollTop >= scrollTop) {
console.log('done');
console.log(img.src);
console.log(!img.src);
// 通过自定义属性 done 来控制懒加载的触发
if (!img.getAttribute('done')) {
// 加载图片
// 读取资源url
let url = img.getAttribute('data-src')
// 赋值图片 url 赋值后 图片会被自动加载
img.src = url
img.setAttribute('done', 'true')
}
}
})
</script>
6,总结
bootstrap和react都很好用,其中,bootstrap主要是框架,使用bootstrap可以帮助我们节约许多的代码。可以让我们的代码量更加的短
而react分为声明式和组件式
其中声明式也就是js中的数据决定页面最终渲染的结果
组件式组件化的思想可以将复杂页面,化繁为简的进行设计,并且组件可提高代码复用性
这两个在平时写前端的时候应该都能用的比较平凡