写前话痨
为啥俺开始学习React了,讲白了,就是为了适应这个 卷的时代 ,既然俺不能也不可能让这个时代去适应俺,那俺就能 热脸贴着冷时代(冷屁股) 了,还是嘎嘎会贴的那种😁
个人理解
俺之前的主打一直是vue,项目也都是围绕vue写的,其他的框架学了也用不上,纯属浪费时间。但现在很多招聘信息上都加上 Vue + React
,不学不行呀,像我这种菜鸟程序员,只会 Ctrl+C+V
的那种,不多学点技术绑在身上,出去就是饿死🙄
接触了一段时间的React开发后,从刚开始的不太适应(毕竟用Vue有小几年了),到慢慢觉得,你还别说,还真滴挺香的😍,有点爱不释手了,真的。但要想取代俺的主打框架Vue,还有待进一步验证。
学前准备
- 开发工具:
VSCode
; - 开发环境: 俺的node版本是16.20.0的,个人建议版本
>=16.x
就行了 Node.js (nodejs.org) ; - 俺叫它代码提交工具:
git
Git (git-scm.com) ; - VSCode插件拓展:
Simple React Snippets,可通过缩写快速生成React相关的代码片段
Auto Import - ES6, TS, JSX, TSX,可自动引入React相关依赖
Prettier - Code formatter,可自动格式化代码
Error Lens,错误行代码高亮提示错误
CodeGeeX: AI Code AutoComplete, Chat, Auto Comment,代码智能提示工具
Auto Close Tag,自动关闭标签,在开始标记的结束括号中键入后,将自动插入结束标记
ESLint,自动检测不符合规范的代码
JavaScript (ES6) code snippets,ES6 语法中的 JavaScript 代码片段提示
Night Owl,设置编辑器颜色主题
- node版本管理工具: 两种工具都可,看个人意愿吧,俺用的是
nvm
nvm
nvm for windows 下载、安装及使用 - 掘金 (juejin.cn) ;volta
还在用nvm做node管理工具?快来试试Volta吧! - 掘金 (juejin.cn) ;
开始入坑
网上有很多对React的介绍,俺这就不多说了。这里俺一个不吱声,直接给俺看官网: React ; 直到看到它想吐,那就牛逼了😎
create-react-app 脚手架
- 采用脚手架创建项目:react-basics
bash
$ npx create-react-app react-basics
将代码和文件该删的删,该留的留: 如下结构
App.js为根组件,并在index.js文件中渲染。
React三大API
在 index.js
中就可以看到两个:
js
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
ReactDOM.createRoot(): 获取根元素,根元素就是React元素要插入的位置
root.render(): 用来将React元素渲染到根元素中,根元素中所有的内容都会被删除,被React元素所替换,当重复调用render()时,React会将两次的渲染结果进行比较,他会确保只修改那些发生变化的元素,对DOM做最少的修改。
除了这两个还有一个:- React.createElement(): 用来出创建一个React元素:
参数:
-
元素名(组件名、html标签必须小写)
-
元素的属性
-
在设置事件时
- 事件名使用驼峰命名法,后面跟事件函数
- 在设置class时,class使用className
-
元素的子元素(内容)
注意点
:React元素最终会通过虚拟DOM转化为真实的DOM元素;React元素一旦创建无法修改,只能通过新创建的元素进行替换,例如:
js
// 创建一个div
const div = React.createElement("div", {}, "Hello World", button);
JSX注意事项
-
JSX不是字符串,不要加引号
-
JSX中html标签应该小写,React组件应该大写开头
-
JSX中有且只有一个根标签
-
JSX的标签必须正确结束(自结束标签必须写/)
-
在JSX中可以使用{}嵌入表达式
有值的语句的就是表达式
-
如果表达式是空值、布尔值、undefined,将不会显示
-
在JSX中,属性可以直接在标签中设置
注意:
class需要使用className代替
style中必须使用对象设置style={{background:'red'}}
-
渲染列表:{} 只能用来放js表达式,而不能放语句(if for),但在语句中是可以去操作JSX
虚拟DOM
在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,React通过虚拟DOM 将React元素 和 原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真实DOM中体现出来。
虚拟DOM的好处:
- 降低API复杂度
- 解决兼容问题
- 提升性能(减少DOM的不必要操作)
每当我们调用root.render()时,页面就会发生重新渲染,React会通过diffing算法,将新的元素和旧的元素进行比较,通过比较找到发生变化的元素,并且只对变化的元素进行修改,没有发生的变化不予处理
例子:将 const data = ['孙悟空', '猪八戒', '沙和尚'];
用列表展示:
js
function App() {
const data = ["孙悟空", "猪八戒", "沙和尚"];
return (
<div className="App">
hello world!
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;
当重新再用当前一样的数据再渲染一次,比较两次数据时,React会先比较父元素,父元素如果不同,直接所有元素全部替换,父元素一致,在去逐个比较子元素,直到找到所有发生变化的元素为止,上例中,新旧两组数据完全一致,所以没有任何DOM对象被修改。
当我们在JSX中显示数组时,数组中每一个元素都需要设置一个唯一key,否则控制台会显示红色警告。
当在列表的最后添加了一个新元素时:
js
function App() {
const data = ["孙悟空", "猪八戒", "沙和尚"];
const addDataHandler = () => {
data.push("唐三藏");
console.log(data);
};
return (
<div className="App">
hello world!
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={addDataHandler}>添加</button>
</div>
);
}
export default App;
运行项目,打开控制台:
state 数据发生变化时自动重新渲染
你看,俺也向末尾添加数据了呀,为啥没有显示到页面去呢,你重写刷新页面也不行,data
是个死数据。这时我们就需要 state 数据发生变化时自动重新渲染:
在React中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染
要使得组件可以收到变量的影响,必须在变量修改后对组件进行重新渲染
这里我们就需要一个特殊变量,当这个变量被修改时,组件会自动重新渲染
state相当于一个变量:
- state只属于当前组件,其他组件无法访问。并且state是可变的,当其发生变化后组件会自动> 重新渲染,以使变化在页面中呈现
- 只是这个变量在React中进行了注册,React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染,使得我们的修改可以在页面中呈现出来
在函数组件中,我们需要通过钩子函数,获取state,使用钩子 useState() 来创建state,import {useState} from "react";
它需要一个值作为参数,这个值就是state的初始值,该函数会返回一个数组,数组中第一个元素,是初始值,初始值只用来显示数据,直接修改不会触发组件的重新渲染,数组中的第二个元素,是一个函数,通常会命名为setXxx
这个函数用来修改state,调用其修改state后会触发组件的重新渲染,并且使用函数中的值作为新的state值
state 的问题
- state实际就是一个被React管理的变量,当我们通过setState()修改变量的值时,会触发组件的自动重新渲染
- 只有state值发生变化时,组件才会重新渲染
- 当state的值是一个对象时,修改时是使用新的对象去替换已有对象
- 当通过setState去修改一个state时,并不表示修改当前的state,它修改的是组件下一次渲染时state值
- setState()会触发组件的重新渲染,它是异步的,所以当调用setState()需要用旧state的值时,一定要注意,有可能出现计算错误的情况,为了避免这种情况,可以通过为setState()传递回调函数的形式来修改
来尝尝鲜:
js
import { useState } from "react";
function App() {
const [data, setData] = useState(["孙悟空", "猪八戒", "沙和尚"]);
const addDataHandler = () => {
// prevData为旧的data
setData((prevData) => {
return [...prevData, "唐僧"];
});
};
console.log(data);
return (
<div className="App">
hello world!
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={addDataHandler}>添加</button>
</div>
);
}
export default App;
你会看到,在列表的最后添加了一个新元素,并没有改变其他的元素的顺序,在控制台中你会看到只是最后追加了新的 li ,所以这种操作不会带来性能问题。
但当在列表的最前边插入了一个新元素,你在看看:就是将 [...prevData, "唐僧"]
调换下位置
js
setData((prevData) => {
return ["唐僧", ...prevData];
});
你看看,在列表的最前边插入了一个新元素,其他元素内容并没有发生变化,但是由于新元素插入到了开始位置,其余元素的位置全都发生变化,而React默认是根据位置比较元素,所以 此时,所有元素都会被修改,为了解决这个问题,React为列表设计了一个key属性,key的作用相当于ID,只是无法在页面中查看,当设置key以后,再比较元素时,就会比较相同key的元素,而不是按照顺序进行比较,在渲染一个列表时,通常会给列表项设置一个唯一的key来避免上述问题(这个key在当前列表中唯一即可)。
注意:
- 开发中一般会采用数据的id作为key
- 尽量不要使用元素的index作为key
- 索引会跟着元素顺序的改变而改变,所以使用索引做key跟没有key是一样的
- 唯一的不同就是,控制台的警告没了
- 当元素的顺序不会发生变化时,用索引做key也没有什么问题
下集精彩
屁股坐疼了,需要出门透透气,理解一下好不啦😁。下篇俺会讲到哪些知识点呢: 可能这篇已经涉及到一些下一篇的知识点了,没事,不用知道那些是为啥,只要懂得这篇的知识点就很棒了
- React如何组件化:在代码示例中你可能也会看到组件化的大致结构长啥样了;
- React如何定义事件:在代码示例中的button就添加了一个点击事件;
- React如何组件通信;
- React如何如何获取真实的DOM;