本人主要从事Java,仅对react进行学习与了解,前端水平一般,如有错误之处,请指定,视频学习过程的笔记,方便自己查阅,也希望对你有帮助。
声明:以下笔记内容均摘自老师的讲课视频,仅供学习使用
以下为React视频教程学习笔记,写此笔记目的为通过学习与手敲一遍老师的笔记,加深印象,也方便查阅。
视频地址:React18视频教程(讲师:李立超)React视频教程
var、let、constant三种声明方式
三种声明方式中,第一优先使用的是const,如果希望变量被改变则使用let,至于var能不用就不用,最好不要在代码中出现。
var:没有块级作用域
let:有块级作用域
const:和let类似,具有块级作用域,但是它只能赋值一次
const使用场景:对于一些常量可以使用const声明,对于一些对象(函数)也可以使用const声明,这样可以避免对象(函数)被意外修改
示例
{
let a = 10;
}
// console.log(a);
for(let i=0; i<5; i++){
console.log('循环内部-->', i);
}
// console.log('循环外部-->', i);
(function (){
if(false){
var b = 33;
}
})();
if(false){
let b = 33;
}
// console.log(b);
const c = 44;
// c = 'hello';
const PI = 3.14;
// console.log(c);
const obj = {name:'孙悟空'};
obj.age = 18;
const fn = function (){
};
console.log(obj.age);
通过解构赋值,可以将数组中的元素,对象中的属性赋值给其他的变量
元素解构赋值示例
let arr = ['孙悟空', '猪八戒'];
[a, b] = arr;
const [a, b] = arr;
#函数的返回值是数组,也可以解构赋值
function fn() {
return ['沙和尚', '唐僧'];
}
const [a, b] = fn();
arr = ['孙悟空', '猪八戒', '沙和尚', '唐僧'];
// const [a, b, ,c] = arr; // 可以跳过元素
// const [a, b, ...c ] = arr; // ...变量,会接收后边所有的元素
// console.log('a='+a, 'b='+b, 'c='+c);
const obj = {
name: '孙悟空',
age: 18,
gender: '男'
};
let a, b, c;
// ({name:a, age:b, gender:c} = obj); // 将name赋值给a,age赋值给b,gender赋值给c
const {name, gender, age} = obj; // 如果变量名和属性名一致,则可以只写一个
// console.log(a, b, c);
// console.log(name, age, gender);
// 利用数组的解构来交换两个变量的位置
a = 10;
b = 20;
[a, b] = [b, a]; // 交换变量a和b的位置
arr = [1, 3, 2];
[arr[1], arr[2]] = [arr[2], arr[1]]; // 交换数组中两个元素的位置
// console.log('a =', a);
// console.log('b =', b);
console.log(arr);
可以通过 ... 展开一个数组
function fn(a, b, c) {
return a + b + c;
}
const arr = [1, 2, 3];
// 计算数组中三个数字的和
let result = fn(...arr);
// console.log(result);
// const arr2 = [...arr]; // 相当于将arr浅复制给arr2
const arr2 = [7, 8, 9, ...arr, 4, 5, 6];
// console.log(arr2);
const obj = {
name: '孙悟空',
age: 18,
gender: '男'
};
// const obj2 = {...obj}; // 将obj在新的对象中展开,相当于浅复制
const obj2 = {address: '花果山', ...obj, name: '猪八戒',};
console.log(obj2);
/*
* 箭头函数
* - 只有一个参数的函数
* 参数 => 返回值
* - 如果没有参数,或多个参数,参数需要使用()括起来
* () => 返回值
* (a, b, c) => 返回值
* - 箭头后边的值就是函数的返回值
* - 返回值必须是一个表达式(有值的语句)
* - 如果返回值是对象,必须加()
* - 如果需要在箭头函数中定义逻辑,可以直接在箭头后跟一个代码块,
* 代码块中语法和普通函数没有区别
* */
示例:
const fn = function (a){
return 'hello';
};
const fn2 = a => a+'hello';
const fn3 = (a, b) => a+'hello';
// console.log(fn2(123));
const sum = (a, b) => a + b;
let result = sum(123, 456);
const fn4 = (a, b) => {
if(a === 10){
a += 5;
}else if(a === 20){
a += 10;
}
return a + b;
};
result = fn4(10, 5);
console.log(result);
/*
* 1.箭头函数中没有arguments
* 2.箭头函数中没有自己的this
* - 它的this总是外层作用域的this
* 3.箭头函数中的this无法通过call()、apply()、bind()修改
* 4.箭头函数无法作为构造函数使用
*
* */
示例:
function fn(a, b, ...args){
// arguments用来保存函数的实参
// console.log(arguments.length);
console.log(args);
}
const fn2 = (...args)=>{
console.log(args);
};
const fn3 = () => {
console.log(this);
};
const obj = {
hello:()=>{
console.log(this);
}
};
const obj2 = {
hello:function (){
const test = () => {
console.log('-->',this);
};
test();
}
};
obj2.hello();
// new fn3();
/*
* 类
* - 类是对象的模板
* - 类决定了一个对象中有哪些属性和方法
* - 使用class关键字来定义一个类
* */
示例:
class Person{
// 可以直接在类中定义属性
// name = '孙悟空';
// age = 18;
// 构造函数
// 当我们通过new创建对象时,实际上就是在调用类的构造函数
constructor(name, age) {
// 将参数赋值给对象中的属性
// 在构造函数中,可以通过this来引用当前的对象
// 在构造函数中定义属性
this.name = name;
this.age = age;
}
// 定义实例方法
run(){
console.log('我会跑!');
}
}
const per = new Person('孙悟空', 18);
const per2 = new Person('猪八戒', 28);
//
console.log(per);
console.log(per2);
// console.log(per === per2);
//
// per.run();
/*
* 类中的所有代码都会在严格模式下执行
* 严格模式下其中一个特点就是,函数的this不在是window,而是undefined
*
* 注意:
* 在类中方法的this不是固定的
* 以方法形式调用时,this就是当前的实例
* 以函数形式调用,this是undefined
* 在开发时,在有些场景下,我们希望方法中的this是固定的,不会因调用方式不同而改变
* 如果遇到上述需求,可以使用箭头函数来定义类中的方法
* 如果类中的方法是以箭头函数定义的,则方法中的this恒为当前实例,不会改变
*
* */
示例
class MyClass{
constructor() {
//this.fn = this.fn.bind(this); //将fn方法的this绑定为当前实例
}
fn(){
console.log('-->',this);
}
// fn = () => {
// console.log('-->',this);
// };
}
const mc = new MyClass();
const test = mc.fn;
mc.fn(); // mc
test(); // undefined
const fn2 = function (){
console.log(this);
};
// fn2();
// 通过继承可以使得类中拥有其他类中的属性和方法
// 使用extends来继承一个类,继承后就相当于将该类的代码复制到了当前类中
// 当我们使用继承后,被继承的类就称为父类,继承父类的类 称为子类
// 将多个类中的重复代码提取出来
class Animal{
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello = () => {
console.log('动物在叫');
};
}
class Dog extends Animal{
/*
* 子类继承父类后,将获得父类中所有的属性和方法,
* 也可以创建同名的属性或方法来对父类进行重写
* */
sayHello = () => {
console.log('汪汪汪!');
};
}
class Snake extends Animal{
// 当在子类中重写父类构造函数时,必须在子类构造函数中第一时间调用父类构造函数,否则会报错
constructor(name, age, len) {
super(name, age); // 调用父类构造函数
this.len = len;
}
sayHello = () => {
console.log('嘶嘶嘶~~');
};
}
const dog = new Dog('旺财', 5);
const snake = new Snake('长虫', 4, 10);
// console.log(dog.name, dog.age);
console.log(snake.name, snake.age, snake.len);
// dog.sayHello();
snake.sayHello();
/*
* 直接通过类调用的属性和方法被称为静态属性和静态方法
* */
class MyClass {
// 使用static开头的属性是静态属性,方法是静态方法
static name = '哈哈';
static fn = () => {
// 注意:静态方法this不是实例对象而是当前的类对象
console.log(this);
};
}
console.log(MyClass.name);
MyClass.fn();
/*
* map()
* - 可以根据原有数组返回一个新数组
* - 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
* - 回调函数中有三个参数:
* 第一个参数:当前元素
* 第二个参数:当前元素的索引
* 第三个参数:当前数组
*
* filter()
* - 可以从一个数组中获得符和条件的元素
* - 会根据回调函数的结果来决定是否保留元素,true保留,false不保留
*
* find()
* - 可以从一个数组中获得符和条件的第一个元素
*
* reduce()
* - 可以用来合并数组中的元素
* - 需要两个参数:
* 回调函数(指定运算规则)
* 四个参数:
* prev 上一次运算结果
* curr 当前值
* index 当前索引
* arr 当前数组
* 初始值
* - 用来指定第一次运算时prev,如果不指定则直接从第二个元素开始计算
* */
示例
const arr = [1, 2, 3, 4, 5];
let result = arr.map((item) => {
return item + 2;
});
result = arr.map((item, index, array) => {
return item + 2;
});
// console.log(result);
const arr2 = ['孙悟空', '猪八戒', '沙和尚'];
/*
* 孙悟空
* 猪八戒
* 沙和尚
* */
result = arr2.map(item => "" + item + "");
result = arr.filter(item => item % 2 === 0);
result = arr.find(item => item % 2 === 0);
result = arr.reduce((prev, curr, index) => {
console.log(index, prev, curr);
return prev + curr
},0);
console.log(result);
React是一个用于构建用户界面的
Javascript
库,起源于Facebook
,2013年开源。React还有React Native框架,通过它可以直接使用Javascript编写原生应用。
React特点:
虚拟DOM
声明式
基于组件
支持服务器端渲染
快速、简单、易学
通过使用引入外部js的 方式来使用,需要引入
react.development.js
和react-dom.development.js
react.development.js
react是react核心库,只要使用react就必须要引入
下载地址:https://unpkg.com/[email protected]/umd/react.development.js
react-dom.development.js
react-dom 是react的dom包,使用react开发web应用时必须引入
下载地址:https://unpkg.com/[email protected]/umd/react-dom.development.js
注:有可能上面的两个下载地址由于网络原因下载不了,可以直接移动到最后的源码文件里面下载,效果一样。
HelloWorld.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<!--引入react的核心库-->
<script src="script/react.development.js"></script>
<!--引入react的DOM库-->
<script src="script/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<script>
/*
* React就是用来代替DOM的
* */
// 通过DOM向页面中添加一个div
// 创建一个div
// const div = document.createElement('div'); // 创建一个dom元素
// // 向div中设置内容
// div.innerText = '我是一个div';
// // 获取root
// const root = document.getElementById('root');
// // 将div添加到页面中
// root.appendChild(div);
// 通过React向页面中添加一个div
/*
* React.createElement()
* - 用来创建一个React元素
* - 参数:
* 1. 元素名(组件名)
* 2. 元素中的属性
* 3. 元素的子元素(内容)
* */
const div = React.createElement('div', {}, '我是React创建的div'); // 创建一个React元素
// 获取根元素对应的React元素
// ReactDOM.createRoot() 用来创建React根元素,需要一个DOM元素作为参数
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将div渲染到根元素中
root.render(div);
</script>
</body>
</html>
/*
* React.createElement()
* - 用来创建一个React元素
* - 参数:
* 1.元素的名称(html标签必须小写)
* 2.标签中的属性
* - class属性需要使用className来设置
* - 在设置事件时,属性名需要修改为驼峰命名法
* 3.元素的内容(子元素)
* - 注意点:
* React元素最终会通过虚拟DOM转换为真实的DOM元素
* React元素一旦创建就无法修改,只能通过新创建的元素进行替换
* */
示例:
// 创建一个React元素
const button = React.createElement('button', {
type: 'button',
className: 'hello',
onClick: () => {
alert('你点我干嘛')
}
}, '点我一下');
// 创建第一个div
const div = React.createElement('div', {}, '我是一个div', button);
// 获取根元素,根元素就是React元素要插入的位置
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将元素在根元素中显示
root.render(div);
// 获取按钮对象
const btn = document.getElementById('btn');
btn.addEventListener('click', ()=>{
// 点击按钮后,修改div中button的文字为click me
const button = React.createElement('button', {
type: 'button',
className: 'hello',
onClick: () => {
alert('你点我干嘛');
}
}, 'click me');
// 创建一个div
const div = React.createElement('div', {}, '我是一个div', button);
// 修改React元素后,必须重新对根元素进行渲染
// 当调用render渲染页面时,React会自动比较两次渲染的元素,只在真实DOM中更新发生变化的部分
// 没发生变化的保持不变
root.render(div);
});
// 将元素在根元素中显示
/*
* root.render()
* - 用来将React元素渲染到根元素中
* - 根元素中所有的内容都会被删除,被React元素所替换
* - 当重复调用render()时,React会将两次的渲染结果进行比较,
* 它会确保只修改那些发生变化的元素,对DOM做最少的修改
* */
解析jsx需要引入
babel
,
另外需要设置script标签属性
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>JSX</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="script/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<!--设置js代码被babel处理-->
<script type="text/babel">
// 创建一个React元素
// 命令式编程
// const button = React.createElement('button', {}, '我是按钮');
// 声明式编程,结果导向的编程
// 在React中可以通过JSX(JS扩展)来创建React元素,JSX需要被翻译为JS代码,才能被React执行
// 要在React中使用JSX,必须引入babel来完成“翻译”工作
// const button = ; // React.createElement('button', {}, '我是按钮');
/*
* JSX就是React.createElement()的语法糖
* JSX在执行之前都会被babel转换为js代码
* */
const div = <div>
我是一个div
<button>我是按钮</button>
</div>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(div);
</script>
</body>
</html>
/*
* JSX的注意事项
* 1. JSX不是字符串,不要加引号
* 2. JSX中html标签应该小写,React组件应该大写开头
* 3. JSX中有且只有一个根标签
* 4. JSX的标签必须正确结束(自结束标签必须写/)
* 5. 在JSX中可以使用{}嵌入表达式
* - 有值的语句的就是表达式
* 6. 如果表达式是空值、布尔值、undefined,将不会显示
* 7. 在JSX中,属性可以直接在标签中设置
* 注意:
* class需要使用className代替
* style中必须使用对象设置
* style={{background:'red'}}
*
* */
/*
* {} 只能用来放js表达式,而不能放语句(if for)
* 在语句中是可以去操作JSX
* */
// const div = Hello {name};
示例:
let div;
if(lang === 'en'){
div = <div>hello {name}</div>;
}else if(lang === 'cn'){
div = <div>你好 {name}</div>;
}
const data = ['孙悟空', '猪八戒', '沙和尚'];
/*
- 孙悟空
- 猪八戒
...
[孙悟空 , 猪八戒 , 沙和尚 ]
* */
// const arr = [];
// 遍历data
// for(let i=0; i
// arr.push({data[i]} );
// }
// const arr = data.map(item => {item} );
// 将arr渲染为一个列表在网页中显示
// jsx中会自动将数组中的元素在页面中显示
// const list = {arr}
;
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(list);
/*
* 在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,
* React通过虚拟DOM 将React元素 和 原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真实DOM中体现出来
* 虚拟DOM的好处:
* 1.降低API复杂度
* 2.解决兼容问题
* 3.提升性能(减少DOM的不必要操作)
*
* 每当我们调用root.render()时,页面就会发生重新渲染
* React会通过diffing算法,将新的元素和旧的元素进行比较
* 通过比较找到发生变化的元素,并且只对变化的元素进行修改,没有发生的变化不予处理
* */
//创建一个数据
const data = ['孙悟空', '猪八戒', '沙和尚'];
// 创建列表
const list =
{/*data.map(item => - {item}
)*/}
{data.map((item, index) => - {item}
)}
;
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染元素
root.render(list);
document.getElementById('btn').onclick = function (){
// 重新渲染页面
//创建一个数据
const data = ['唐僧', '孙悟空', '猪八戒', '沙和尚'];
// 创建列表
const list =
{/*data.map(item => - {item}
)*/}
{data.map((item, index) => - {item}
)}
;
// 渲染元素
root.render(list);
/*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 比较两次数据时,React会先比较父元素,父元素如果不同,直接所有元素全部替换
* 父元素一致,在去逐个比较子元素,直到找到所有发生变化的元素为止
* 上例中,新旧两组数据完全一致,所以没有任何DOM对象被修改
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>tom
* li>猪八戒
* li>沙和尚
*
* 上例中,只有第一个li发生变化,所以只有第一个li被修改,其他元素不变
*
* 当我们在JSX中显示数组中,数组中每一个元素都需要设置一个唯一key,否则控制台会显示红色警告
* 重新渲染页面时,React会按照顺序依次比较对应的元素,当渲染一个列表时如果不指定key,同样也会按照顺序进行比较,
* 如果列表的顺序永远不会发生变化,这么做当然没有问题,但是如果列表的顺序会发生变化,这可能会导致性能问题出现
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* li>唐僧
*
* 上例中,在列表的最后添加了一个新元素,并没有改变其他的元素的顺序,所以这种操作不会带来性能问题
*
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>唐僧
* li>孙悟空
* li>猪八戒
* li>沙和尚
*
* 上例中,在列表的最前边插入了一个新元素,其他元素内容并没有发生变化,
* 但是由于新元素插入到了开始位置,其余元素的位置全都发生变化,而React默认是根据位置比较元素
* 所以 此时,所有元素都会被修改
*
* 为了解决这个问题,React为列表设计了一个key属性,
* key的作用相当于ID,只是无法在页面中查看,当设置key以后,再比较元素时,
* 就会比较相同key的元素,而不是按照顺序进行比较
* 在渲染一个列表时,通常会给列表项设置一个唯一的key来避免上述问题
* (这个key在当前列表中唯一即可)
* 注意:
* 1.开发中一般会采用数据的id作为key
* 2.尽量不要使用元素的index作为key
* 索引会跟着元素顺序的改变而改变,所以使用索引做key跟没有key是一样的
* 唯一的不同就是,控制台的警告没了
* 当元素的顺序不会发生变化时,用索引做key也没有什么问题
* 旧数据
* ul
* li(key=孙悟空)>孙悟空
* li(key=猪八戒)>猪八戒
* li(key=沙和尚)>沙和尚
* 新数据
* ul
* li(key=唐僧)>唐僧
* li(key=孙悟空)>孙悟空
* li(key=猪八戒)>猪八戒
* li(key=沙和尚)>沙和尚
* */
};
后面有一个使用create-react-app自动创建项目的笔记,但老师说为了学习与了解,还是有必要走一遍手动创建的过程
npm init -y
或yarn init -y
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>react_project</title>
</head>
<body>
<div id="root"></div>
<!--
public/index.html是首页的模板,webpack在编译文件时,会以index.html为模板生成index.html
-->
</body>
</html>
index.js
// src/index.js 是js的入口文件
// 引入ReactDOM
import ReactDOM from 'react-dom/client';
// 创建一个JSX
const App = <div>
<h1>这是一个React项目</h1>
</div>;
// 获取一个根容器
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将App渲染进根容器
root.render(App);
npm install react react-dom react-scripts -S
或 yarn add react react-dom react-scripts
npm install react@18.0.0 react-dom@18.0.0 react-scripts@5.0.1 -S
yarn add react@18.0.0 react-dom@18.0.0 react-scripts@5.0.1
npx react-scripts start
启动项目(初始启动需要输入y确认)运行成功后会在package.json自动加入以下配置
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
npm start
或yarn start
了,同样,构建可以直接使用npm build
或yarn build
. "scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
},
http://localhost:3000
进行访问npx react-scripts build
或npm build
或yarn build
进行项目打包构建先参考第21节手工创建一个React项目,接着在package.json中加入eslintConfig
的配置如下(此配置可以进行jsx的语法校验):
"eslintConfig": {
"extends": [
"react-app"
]
},
一般js和css分开写,可以通过import './index.css';
引入样式表
// 引入样式表
import './index.css';
import ReactDOM from 'react-dom/client';
// 引入样式表
import './index.css';
// 创建一个React元素
const App = <div className="logs">
{/* 日志项容器 */}
<div className="item">
{/* 日期的容器 */}
<div className="date">
<div className="month">
四月
</div>
<div className="day">
19
</div>
</div>
{/* 日志内容的容器 */}
<div className="content">
<h2 className="desc">学习React</h2>
<div className="time">40分钟</div>
</div>
</div>
</div>;
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染元素
root.render(App);
/*
* 组件
* - React中组件有两种创建方式
* - 函数式组件
* - 函数组件就是一个返回JSX的普通
* - 组件的首字母必须是大写
* - 类组件
* */
函数组件定义例子(App.js):
const App = () => {
return <div>我是App组件!</div>
};
// 导出App
export default App;
/*
* 类组件必须要继承React.Component
* 相较于函数组件,类组件的编写要麻烦一下,
* 但是他俩的功能是一样的
* */
import React from "react";
class App extends React.Component{
// 类组件中,必须添加一个render()方法,且方法的返回值要是一个jsx
render() {
return <div>我是一个类组件</div>;
}
}
// 导出App
export default App;
普通的Js的事件处理有直接在标签上写onclick
、onclick = function(){}
、和addEventListener
等方式,如下:
/**
**
** document.getElementById('btn01').onclick = function(){};
* document.getElementById('btn01').addEventListener('click', function(){}, false);
* */
取消默认行为
event.preventDefault(); // 取消默认行为
取消事件的冒泡
event.stopPropagation();
/*
* 在React中,无法通过return false取消默认行为
* return false;
* * 事件对象
* - React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象
* - React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象
* - 由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题
* */
在React中事件需要通过元素的属性来设置,
和原生JS不同,在React中事件的属性需要使用驼峰命名法:
onclick -> onClick
onchange -> onChange
属性值不能直接执行代码,而是需要一个回调函数:
onclick="alert(123)"
onClick={()=>{alert(123)}}
// 在函数组件中,属性就相当于是函数的参数,可以通过参数来访问
// 可以在函数组件的形参中定义一个props,props指向的是一个对象
// 它包含了父组件中传递的所有参数
在父组件中设置属性,可能是属性值,也可以是函数,子组件传参数给父组件可以通过父组传传递一个函数后,由子组件调用把数据传给父组件.
const Logs = () => {
return <div className="logs">
{/*在父组件中可以直接在子组件中设置属性*/}
{/*{}} />*/ }
<LogItem date={new Date()} desc={"学习前端"} time={"50"} />
<LogItem date={new Date()} desc={"哈哈"} time={"30"} />
</div>
};
在子组件中接收属性
const LogItem = (props) => {
return (
<div className="item">
<MyDate/>
{/* 日志内容的容器 */}
<div className="content">
{/*
如果将组件中的数据全部写死,将会导致组件无法动态设置,不具有使用价值
我们希望组件数据可以由外部设置,在组件间,父组件可以通过props(属性)向子组件传递数据
*/}
<h2 className="desc">{props.desc}</h2>
<div className="time">{props.time}分钟</div>
</div>
</div>
);
};
此节将日期变成动态数据,如在父组件中传参数
<LogItem date={new Date(2021,7,20,19,0)} desc={"学习前端"} time={"50"} />
<LogItem date={new Date(2022,5,22,5,30)} desc={"哈哈"} time={"30"} />
在子组件中接收参数进行解析
//console.log(props.date.getDate());
// 获取月份
const month = props.date.toLocaleString('zh-CN', {month:'long'});
// 获取日期
const date = props.date.getDate();
接着在return中接收并使用
return (
<div className="date">
<div className="month">
{month}
</div>
<div className="day">
{date}
</div>
</div>
主要是将列表的静态数据通过遍历的方式渲染出来
首先模拟好一组服务器中的数据:
// 模拟一组从服务器中加载的数据
const logsData = [
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
];
接着通过遍历与jsx语法将数据渲染到页面
,可以在return之外先定义好,也可以直接边遍功边输出,比如logsData.map(item =>
,采用了展开语法将item
中的属性全部传递到LogItem组件中
// 将数据放入JSX中
const logItemDate = logsData.map(item => <LogItem key={item.id} date={item.date} desc={item.desc} time={item.time}/>);
return <div className="logs">
{
logItemDate
//logsData.map(item => )
}
</div>
/*
* 在React中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染
* 要使得组件可以收到变量的影响,必须在变量修改后对组件进行重新渲染
* 这里我们就需要一个特殊变量,当这个变量被修改使,组件会自动重新渲染
*
* state相当于一个变量,
* 只是这个变量在React中进行了注册,
* React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染
* 使得我们的修改可以在页面中呈现出来
*
* 在函数组件中,我们需要通过钩子函数,获取state
*
* 使用钩子 useState() 来创建state
* import {useState} from "react";
*
* 它需要一个值作为参数,这个值就是state的初始值
* 该函数会返回一个数组
* 数组中第一个元素,是初始值
* - 初始值只用来显示数据,直接修改不会触发组件的重新渲染
* 数组中的第二个元素,是一个函数,通常会命名为setXxx
* - 这个函数用来修改state,调用其修改state后会触发组件的重新渲染,
* 并且使用函数中的值作为新的state值
* */
使用示例:
import './App.css';
import {useState} from "react";
const App = () => {
console.log('函数执行了 ---> 组件创建完毕!');
const [counter, setCounter] = useState(1);
// let counter = result[0];
// let setCounter = result[1];
// const [counter, setCounter] = result;
/*
* 当点击+时,数字增大
* 点击-时,数字减少
* */
// 创建一个变量存储数字
// let counter = 2;
const addHandler = () => {
// 点击后数字+1
// alert('+1');
// counter++;
setCounter(counter + 1); // 将counter值修改为2
};
const lessHandler = () => {
// 点击后数字-1
// alert('-1');
// counter--;
setCounter(counter-1);
};
return <div className={'app'}>
<h1>{counter}</h1>
<button onClick={lessHandler}>-</button>
<button onClick={addHandler}>+</button>
</div>;
};
// 导出App
export default App;
/*
* state
* - state实际就是一个被React管理的变量
* 当我们通过setState()修改变量的值时,会触发组件的自动重新渲染
* - 只有state值发生变化时,组件才会重新渲染
* - 当state的值是一个对象时,修改时是使用新的对象去替换已有对象
* - 当通过setState去修改一个state时,并不表示修改当前的state
* 它修改的是组件下一次渲染时state值
* - setState()会触发组件的重新渲染,它是异步的
* 所以当调用setState()需要用旧state的值时,一定要注意
* 有可能出现计算错误的情况
* 为了避免这种情况,可以通过为setState()传递回调函数的形式来修改state值
* */
setState可以将最新的state值作为参数传递
setCounter((prevCounter)=>{
/*
* setState()中回调函数的返回值将会成为新的state值
* 回调函数执行时,React会将最新的state值作为参数传递
* */
return prevCounter + 1;
});
注意:修改State时,不能直接修改旧的State对象
// 如果直接修改旧的state对象,由于对象还是那个对象,所以不会生效
// user.name = '猪八戒';
// console.log(user);
// setUser(user);
// const newUser = Object.assign({}, user);
// newUser.name = '猪八戒';
// setUser(newUser);
可以使用展开符将旧的State展开后再组装,比如
setUser({...user, name: '猪八戒'});
/*
* 获取原生的DOM对象
* 1.可以使用传统的document来对DOM进行操作
* 2.直接从React处获取DOM对象
* 步骤:
* 1.创建一个存储DOM对象的容器
* - 使用 useRef() 钩子函数
* 钩子函数的注意事项:
* ① React中的钩子函数只能用于函数组件或自定义钩子
* ② 钩子函数只能直接在函数组件中调用
* 2.将容器设置为想要获取DOM对象元素的ref属性
* ....
* - React会自动将当前元素的DOM对象,设置为容器current属性
*
* useRef()
* - 返回的就是一个普通的JS对象
* - {current:undefined}
* - 所以我们直接创建一个js对象,也可以代替useRef()
* - 区别:
* 我们创建的对象,组件每次重新渲染都会创建一个新对象
* useRef()创建的对象,可以确保每次渲染获取到的都是同一个对象
*
* - 当你需要一个对象不会因为组件的重新渲染而改变时,就可以使用useRef()
*
* */
const h1Ref = useRef(); // 创建一个容器
const [count, setCount] = useState(1);
// const h1Ref = {current:null};
// console.log(temp === h1Ref);
// temp = h1Ref;
const clickHandler = () => {
// 通过id获取h1
const header = document.getElementById('header');
// alert(header);
// header.innerHTML = '哈哈';
console.log(h1Ref);
// alert(h1Ref.current === header);
h1Ref.current.innerText = '嘻嘻!';
};
const countAddHandler = ()=>{
setCount(prevState => prevState + 1);
};
return <div className={'app'}>
<h1 id="header" ref={h1Ref}>我是标题{count}</h1>
<button onClick={clickHandler}>1</button>
<button onClick={countAddHandler}>2</button>
</div>;
推荐安装插件ES7+ React/Redux/React-Native snippets
插件,可以在使用vscode开发react应用时,比如,rcc(React Class Component);rfc(React Functional Component);clg(console.log())等等。
/*
* 类组件的props是存储到类的实例对象中,
* 可以直接通过实例对象访问
* this.props
* 类组件中state统一存储到了实例对象的state属性中
* 可以通过 this.state来访问
* 通过this.setState()对其进行修改
* 当我们通过this.setState()修改state时,
* React只会修改设置了的属性
*
* 函数组件中,响应函数直接以函数的形式定义在组件中,
* 但是在类组件中,响应函数是以类的方法来定义,之前的属性都会保留
* 但是这你仅限于直接存储于state中的属性
*
* 获取DOM对象
* 1.创建一个属性,用来存储DOM对象
* divRef = React.createRef();
* 2.将这个属性设置为指定元素的ref值
* */
类使用示例代码:
import React, {Component} from 'react';
class User extends Component {
// 创建属性存储DOM对象
divRef = React.createRef();
// 向state中添加属性
state = {
count: 0,
test: '哈哈',
obj: {name: '孙悟空', age: 18}
};
// 为了省事,在类组件中响应函数都应该以箭头函数的形式定义
clickHandler = () => {
// this.setState({count: 10});
// this.setState(prevState => {
// return {
// count: prevState + 1
// }
// });
/*this.setState({
obj:{...this.state.obj, name:'沙和尚'}
});*/
console.log(this.divRef);
};
render() {
// console.log(this.props);
// console.log(this.divRef);
return (
<div ref={this.divRef}>
<h1>{this.state.count} --- {this.state.test}</h1>
<h2>{this.state.obj.name} --- {this.state.obj.age}</h2>
<button onClick={this.clickHandler}>点</button>
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.gender}</li>
</ul>
</div>
);
}
}
export default User;
这一节主要是封装一个带边框和圆角的Card容器组件,其他的组件都可以作为其子组件包裹进去使用。关键点是props.children
,表示组件的标签体
Card.js:
import React from 'react';
import './Card.css';
const Card = (props) => {
/*
* props.children 表示组件的标签体
* */
// console.log(props.children);
return <div className={`card ${props.className}`}>{props.children}</div>;
};
export default Card;
Card.css:
.card{
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,.2);
}
可通过在表单域上的onChange事件定义一个函数,在处理函数里面能获取到表单域中的数据,如:
<input onChange={descChangeHandler} id="desc" type="text"/>
let inputDesc = '';
// 监听内容的变化
const descChangeHandler = (e) => {
// 获取到当前触发事件的对象
// 事件对象中保存了当前事件触发时的所有信息
// event.target 执行的是触发事件的对象(DOM对象)
//console.log(e.target.value);
inputDesc = e.target.value;
};
如果表单是通过异步提交请求,需要取消表单的默认提交行为
,通过e.preventDefault();
来实现
// 当表单提交时,汇总表单中的数据
/* 在React中,通常表单不需要自行提交
* 而是要通过React提交
* */
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime
};
console.log(newLog);
};
将表单中的数据存储到state中
,修改数据就调用setState
方法。
/*
* 我们可以将表单中的数据存储到state中,
* 然后将state设置为表单项value值,
* 这样当表单项发生变化,state会随之变化,
* 反之,state发生变化,表单项也会跟着改变,这种操作我们就称为双向绑定
* 这样一来,表单就成为了一个受控组件
* */
const [inputTime, setInputTime] = useState('');
//监听时长的变化
const timeChangeHandler = (e) => {
// 获取到当前触发事件的对象
// 事件对象中保存了当前事件触发时的所有信息
// event.target 执行的是触发事件的对象(DOM对象)
//console.log(e.target.value);
setInputTime(e.target.value);
};
往往我们要维护与管理的数据会很多,可以将数据都放在同一个state
比如修改前有三个数据,开始定义成:
const [inputDate, setInputDate] = useState('');
const [inputDesc, setInputDesc] = useState('');
const [inputTime, setInputTime] = useState('');
可以修改成:
// 将表单数据统一到一个state中
const [formData, setFormData] = useState({
inputDate:'',
inputDesc:'',
inputTime:''
});
设置新数据时,需要注意将其他数据一并带过来,比如下面的...formData
就是先将旧数据复制过来,然后重新设置inputDesc
setFormData({
...formData,
inputDesc: e.target.value
});
在父组件中先定义好要添加的函数saveLogHandler
,传递给子组件中,然后在子组件中进行调用,操作父组件中的方法,更新父组件中的数据list。
/*
* 将LogsForm中的数据传递给App组件,然后App组件,将新的日志添加到数组中!
* */
// 定义一个函数
const saveLogHandler = (newLog) => {
// 向新的日志中添加id
newLog.id = Date.now() + '';
// console.log('App.js -->',newLog);
// 将新的数据添加到数组中
// logsData.push(newLog);
setLogsData([newLog, ...logsData]);
};
return <div className="app">
{/*引入LogsFrom*/}
<LogsForm onSaveLog={saveLogHandler}/>
<Logs logsData={logsData}/>
</div>;
在子组件中进行调用:props.onSaveLog(newLog);
<form onSubmit={formSubmitHandler}>
</form>
// 当表单提交时,汇总表单中的数据
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime
};
// 当要添加新的日志时,调用父组件传递过来的函数
props.onSaveLog(newLog);
};
同完成添加的处理类似
// 定义一个函数,从数据中删除一条日志
const delLogByIndex = (index) => {
setLogsData(prevState => {
const newLog = [...prevState];
newLog.splice(index, 1);
return newLog;
});
};
return <div className="app">
{/*引入LogsFrom*/}
<LogsForm onSaveLog={saveLogHandler}/>
<Logs logsData={logsData} onDelLog={delLogByIndex}/>
</div>;
在Logs组件中使用onDelLog属性接收传进来的函数进行处理
const Logs = (props) => {
// 将数据放入JSX中
const logItemDate = props.logsData.map((item, index) => <LogItem
onDelLog={()=>props.onDelLog(index)}
key={item.id}
date={item.date}
desc={item.desc}
time={item.time}/>);
return <Card className="logs">
{
logItemDate
}
</Card>
};
如果数据为空,logItemData.length === 0
,需要格外处理下,显示没有数据
if (logItemData.length === 0) {
logItemData = <p className="no-logs">没要找到日志!</p>;
}
return <Card className="logs">
{
logItemData
// logItemData.length !== 0 ? logItemData : 没要找到日志!
}
</Card>
1.打开命令行
2.进行到项目所在的目录
3.使用命令`npx create-react-app 项目名`
内联样式
const pStyle = {
color: 'red',
backgroundColor: '#bfa',
border: redBorder ? "red solid 1px" : "blue solid 1px"
};
return (
<div>
<p style={pStyle}>我是一个段落</p>
</div>
);
样式表
样式写在单独的css文件里面,在模块组件中引入
import './App.css';
使用Css_Module可以较好的解决样式冲突问题,做法如下:
/*
* CSS模块
* 使用步骤:
* 1.创建一个xxx.module.css
* 2.在组件中引入css
* import classes from './App.module.css';
* 3.通过classes来设置类
* className={classes.p1}
* CSS模块可以动态的设置唯一的class值
* App_p1__9v2n6
* */
示例:
import React, {useState} from 'react';
import classes from './App.module.css';
const App = () => {
return (
<div>
<p className={`${classes.p1} ${showBorder ? classes.Border : ''}`}>我是一个段落</p>
</div>
);
};
export default App;
Fragment可以包裹子元素,本身又不会创建任何多余的元素。
/*
* React.Fragment
* - 是一个专门用来作为父容器的组件
* 它只会将它里边的子元素直接返回,不会创建任何多余的元素
* - 当我们希望有一个父容器
* 但同时又不希望父容器在网页中产生多余的结构时
* 就可以使用Fragment
* */
如果没有用Framgment
,可以定义一个Out组件
import React from 'react';
const Out = (props) => {
return props.children;
};
export default Out;
使用时:
import React from 'react';
import Out from './Out';
const App = () => {
return (
<Out>
<div>第一个组件</div>
<div>第二个组件</div>
<div>第三个组件</div>
</Out>
);
};
export default App;
有了Fragment后:
import React, { Fragment } from 'react';
const App = () => {
return (
<Fragment>
<div>第一个组件</div>
<div>第二个组件</div>
<div>第三个组件</div>
</Fragment>
);
};
export default App;
或者直接用<>
import React from 'react';
const App = () => {
return (
<>
<div>第一个组件</div>
<div>第二个组件</div>
<div>第三个组件</div>
</>
);
};
export default App;
做一个汉堡到家的app移动应用的练习,即要支持移动应用的开发,需要准备如下
1.index.html中需要设置视口相关的meta属性
<meta name="viewport" content="width=device-width, initial-scale=1"/>
2.调整页面的整体font-size,并让宽度设置为750rem,适配不同的移动分辨率
index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from "./App";
import './index.css';
// 设置移动端的适配
// 除以几视口的宽度就是多少rem,现在我们设置视口的总宽度为750rem
document.documentElement.style.fontSize = 100 / 750 + 'vw';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);
App.js
const App = () => {
return (
<div style={{width: '750rem', height: 200, backgroundColor: '#bfa'}}></div>
);
};
export default App;
3.其他:解决图片默认是基线对齐的缝隙,默认图片采用基线对齐,下面会有缝隙,改vertical-align属性
/**
解决图片默认是基线对齐的缝隙
*/
img{
vertical-align: middle;
}
Font Awesome,一套绝佳的图标字体库和CSS框架,这里说明下怎么在React中使用
/*
* 引入FontAwesome
* - 安装依赖
* npm i --save @fortawesome/fontawesome-svg-core
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/react-fontawesome@latest
yarn add @fortawesome/react-fontawesome@latest @fortawesome/free-regular-svg-icons @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons
- 引入组件
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
- 引入图标
import {faPlus} from "@fortawesome/free-solid-svg-icons";
- 使用组件
*
* */
React Context是React提供的一种跨组件传递数据的机制。它可以避免通过props一层层传递数据,使得组件之间的数据共享更加方便。
/*
* Context相当于一个公共的存储空间,
* 我们可以将多个组件中都需要访问的数据统一存储到一个Context中,
* 这样无需通过props逐层传递,即可使组件访问到这些数据
*
* 通过React.createContext()创建context
*
* */
下面是使用React Context的基本步骤:
1.创建一个Context对象
import React from 'react';
const TestContext = React.createContext({
name:'孙悟空',
age:18
});
export default TestContext;
2.在父组件中提供数据,子组件会找最近的数据,比如下面的B组件,是使用{{name:'沙和尚', age:38}}的数据的
import TestContext from "../store/testContext";
return (
<div>
<TestContext.Provider value={{name:'猪八戒', age:28}}>
<A/>
<TestContext.Provider value={{name:'沙和尚', age:38}} >
<B/>
</TestContext.Provider>
<Meals
mealsData={mealsData}
onAdd={addMealHandler}
onSub={subMealHandler}
/>
</TestContext.Provider>
</div>
);
3.在子组件中使用数据
/*
* 使用方式一:
* 1.引入context
* 2.使用 Xxx.Consumer 组件来创建元素
* Consumer 的标签体需要一个回调函数
* 它会将context设置为回调函数的参数,通过参数就可以访问到context中存储的数据
* */
import TestContext from "../store/testContext";
const A = () => {
return (
<TestContext.Consumer>
{(ctx)=>{
return <div>
{ctx.name} - {ctx.age}
</div>
}}
</TestContext.Consumer>
);
};
export default A;
/*
* 使用Context方式二:
* 1.导入Context
* 2.使用钩子函数useContext()获取到context
* useContext() 需要一个Context作为参数
* 它会将Context中数据获取并作为返回值返回
* Xxx.Provider
* - 表示数据的生产者,可以使用它来指定Context中的数据
* - 通过value来指定Context中存储的数据,
* 这样一来,在该组件的所有的子组件中都可以通过Context来访问它所指定数据
*
* 当我们通过Context访问数据时,他会读取离他最近的Provider中的数据,
* 如果没有Provider,则读取Context中的默认数据
* */
import TestContext from "../store/testContext";
const B = () => {
// 使用钩子函数获取Context
const ctx = useContext(TestContext);
return (
<div>
{ctx.name} -- {ctx.age}
</div>
);
};
export default B;
/*
* 使用Context方式三(类组件):
* 1.导入Context
* 2.声明接收context
* static contextType = TestContext;
* 3.使用数据
* const {name,age} = this.context;
* */
import React, { Component } from 'react';
import TestContext from "../store/testContext";
export default class C extends Component {
//声明接收context
static contextType = TestContext;
render() {
const {name,age} = this.context;
return (
<div>
<h3>我是C组件</h3>
<h4>我接收到的: {name} -- {age} </h4>
</div>
);
}
}
1.创建一个Context对象
import React from 'react';
const CartContext = React.createContext({
items: [],
totalAmount: 0,
totalPrice: 0,
addItem: () => {
},
removeItem: () => {
}
});
export default CartContext;
2.在父组件中提供数据
import React, {useState} from 'react';
import Meals from "./components/Meals/Meals";
import CartContext from "./store/cart-context";
const App = () => {
return (
<CartContext.Provider value={{...cartData, addItem, removeItem}}>
<div>
<Meals
mealsData={mealsData}
/>
</div>
</CartContext.Provider>
);
};
export default App;
3.在子组件中使用数据
import React, {useContext} from 'react';
import CartContext from "../../../store/cart-context";
const Counter = (props) => {
// 获取cartContext
const ctx = useContext(CartContext);
// 添加购物车的函数
const addButtonHandler = () => {
ctx.addItem(props.meal);
};
return (
<div>
<button
onClick={addButtonHandler}
className={classes.Add}>
<FontAwesomeIcon icon={faPlus}/>
</button>
</div>
);
};
export default Counter;
定义一个过滤的函数filterHandler
,此函数通过数组的方法filter进行搜索过滤
// 创建一个过滤meals的函数
const filterHandler = (keyword) => {
const newMealsData = MEALS_DATA.filter(item => item.title.indexOf(keyword) !== -1);
setMealsData(newMealsData);
};
然后将filterHandler
传递给子组件进行调用
<FilterMeals onFilter={filterHandler}/>
在子组件中使用
const FilterMeals = (props) => {
const inputChangeHandler = e => {
const keyword = e.target.value.trim();
props.onFilter(keyword);
};
return (
<div className={classes.FilterMeals}>
<div className={classes.InputOuter}>
<input
onChange={inputChangeHandler}
className={classes.SearchInput}
type="text"
placeholder={"请输入关键字"}/>
</div>
</div>
);
};
为了处理不同的屏幕显示的文字和图片,应该设置字体为相对单位rem
rem
是相对于顶层html元素的大小来的,框架中是这样处理的
// 设置移动端的适配
// 除以几视口的宽度就是多少rem,现在我们设置视口的总宽度为750rem
document.documentElement.style.fontSize = 100 / 750 + 'vw';
设置大小的地方采用rem
来表示,如:
.Price{
font-weight: bold;
font-size: 36rem;
}
通过ReactDOM.createPortal
进行遮罩层的处理,ReactDOM.createPortal是React中的一个方法,它允许你将一个React组件渲染到DOM树的不同部分,超出父组件的层次结构之外。当你想要将组件渲染到DOM树中的特定位置时,它非常有用。
1.定义好backdrop-root
<div id="backdrop-root"></div>
2.渲染到backdrop-root
import React from 'react';
import ReactDOM from 'react-dom';
import classes from './Backdrop.module.css';
const backdropRoot = document.getElementById('backdrop-root');
const Backdrop = (props) => {
return ReactDOM.createPortal(
<div className={`${classes.Backdrop} ${props.className}`}>
{props.children}
</div>,backdropRoot
);
};
export default Backdrop;
/*
* - setState()的执行流程(函数组件)
* setCount() --> dispatchSetDate()
* --> 会先判断,组件当前处于什么阶段
* 如果是渲染阶段 --> 不会检查state值是否相同
* 如果不是渲染阶段 --> 会检查state的值是否相同
* - 如果值不相同,则对组件进行重新渲染
* - 如果值相同,则不对组件进行重新渲染
* 如果值相同,React在一些情况下会继续执行当前组件的渲染
* 但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际的效果
* 这种情况通常发生在值第一次相同时
*
* */
// useEffect()是一个钩子函数,需要一个函数作为参数
// 这个作为参数的函数,将会在组件渲染完毕后执行
// 在开发中,可以将那些会产生副作用的代码编写到useEffect的回调函数中
// 这样就可以避免这些代码影响到组件的渲染
import React, {useEffect, useState} from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(()=>{
setCount(1);
});
return (
<div>
{count}
</div>
);
};
export default App;
/*
* 默认情况下,useEffect()中的函数,会在组件渲染完成后调用,
* 并且是每次渲染完成后都会调用
*
* 在useEffect()可以传递一个第二个参数,
* 第二个参数是一个数组,在数组中可以指定Effect的依赖项
* 指定后,只有当依赖发生变化时,Effect才会被触发
* 通常会将Effect中使用的所有的局部变量都设置为依赖项
* 这样一来可以确保这些值发生变化时,会触发Effect的执行
* 像setState()是由钩子函数useState()生成的
* useState()会确保组件的每次渲染都会获取到相同setState()对象
* 所以setState()方法可以不设置到依赖中
* 如果依赖项设置了一个空数组,则意味Effect只会在组件初始化时触发一次
* */
useEffect(()=>{
if(ctx.totalAmount === 0){
// 购物车已经被清空
setShowDetails(false);
setShowCheckout(false);
}
},[ctx]);
通过useEffect的清理函数来降低执行的次数。
// 通过Effect来改造练习
useEffect(()=>{
// 降低数据过滤的次数,提高用户体验
// 用户输入完了你在过滤,用户输入的过程中,不要过滤
// 当用户停止输入动作1秒后,我们才做查询
// 在开启一个定时器的同时,应该关掉上一次
const timer = setTimeout(()=>{
console.log('Effect触发了!');
props.onFilter(keyword);
}, 1000);
// 在Effect的回调函数中,可以指定一个函数作为返回值
// 这个函数可以称其为清理函数,它会在下次Effect执行前调用
// 可以在这个函数中,做一些工作来清除上次Effect执行所带来的的影响
return () => {
clearTimeout(timer);
};
}, [keyword]);
未完待续,可详见源码下载里面
github: https://github.com/jxlhljh/liuuistudytest/tree/master/liureacttest/react_hook_test
gitee: https://gitee.com/jxlhljh/liuuistudytest/tree/master/liureacttest/react_hook_test