一、基础知识
- 基于 React 18.x
- 官网:https://zh-hans.react.dev
- 开发 React 必须依赖的三个库:
- react:包含react(web和react-native)所必须的核心代码
- react-dom:react渲染在不同平台所需要的核心代码
- web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
- native端:react-dom会将jsx最终渲染成原生的控件(比如Android中的Button,IOS中的UIButton)。
- babel:将jsx转换成React代码的工具
- 又名 Babel.js。 是目前前端使用非常广泛的编译器、转移器。
- 比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它。
- 那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。
- React的依赖引入:这里有一个crossorigin的属性,这个属性的目的是为了拿到跨域脚本的错误信息
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
- 下面的代码示例如没有特殊说明均需要引用上边三个文件
二、JSX 知识
(一) 认识 JSX
- JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
- 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
- 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);
- React认为渲染逻辑本质上与其他UI逻辑存在内在耦合,他们之间是密不可分,所以React没有将html,js,css分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
- JSX的书写规范:
- JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面我们学习的Fragment);
- 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
- JSX中的标签可以是单标签,也可以是双标签;注意:如果是单标签,必须以/>结尾;
<div id="root"></div> <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin ></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin ></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <!-- type="text/babel" 作用是可以让 babel 解析 jsx 的语法 --> <script type="text/babel"> let msg = 'Hello world'; // jsx 语法 // 它不是一段字符串(因为没有使用引号包裹); const element = ( <div> <div>{msg}</div> <div>固定显示</div> </div> ); // 渲染 jsx 语法代表的元素 // ReactDOM 来源于react-dom.js 文件 const root = ReactDOM.createRoot(document.getElementById('root')); root.render(element); </script>
(二) JSX 注释
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const element = (
<div>
{/* 这是一条注释 */}
<div>一条信息</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
(三) JSX 嵌入变量作为子元素
【1】 Number、String、Array 类型
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const num = 1;
const str = '2';
const arr1 = [1, 2, 3];
// 报错
// const arr2 = [{ a: 1 }, { b: 2 }];
const arr3 = [[1,2], [3,4]];
const element = (
<div>
{/* Number */}
<div>{1}</div>
<div>{num}</div>
{/* String */}
<div>{'2'}</div>
<div>{str}</div>
{/* Array */}
<div>{['a', 'b', 'c']}</div>
<div>{arr1}</div>
{/* 1234 */}
<div>{arr3}</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
【2】 null、undefined、Boolean 类型
- 当变量是null、undefined、Boolean类型时,在浏览器上将显示空白;
- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
- 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const und = undefined;
const nul = null;
const truth = true;
const element = (
<div>
{/* undefined */}
{/* 显示空白 */}
<div>{undefined}</div>
<div>{und}</div>
{/* null */}
{/* 显示空白 */}
<div>{null}</div>
<div>{nul}</div>
{/* boolean */}
{/* 显示空白 */}
<div>{truth}</div>
<div>{false}</div>
{/* 显示字符串:false */}
<div>{false + ''}</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
【3】 Object 对象类型
-
Object对象类型不能作为子元素(not valid as a React child)
<div id="root"></div> <!-- 需要引入react文件 --> <script type="text/babel"> const obj = { name: '张三', age: 52 }; const element = ( <div> {/*报错:Uncaught Error: Objects are not valid as a React child */} {/*<div>{obj}</div>*/} <div>{obj.name}</div> <div>{obj.age}</div> </div> ); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(element); </script>
(四) JSX 嵌入表达式
-
运算表达式
-
三元运算符
-
执行一个函数
<div id="root"></div> <!-- 需要引入react文件 --> <script type="text/babel"> let a = true; const arr = ['张三', '里斯', '王五']; function getNames() { const newArr = arr.map((item, index) => { return <li key={index}>{item + index}</li>; }); return newArr; } const element = ( <div> <div>{1 + 1}</div> <div>{a ? 3 : 4}</div> {/*注意:这里使用的是map,即实际插入的是map处理后的返回的数组*/} <ul> {arr.map((item) => ( <li key={item}>{item}</li> ))} </ul> <ul>{getNames()}</ul> </div> ); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(element); </script>
(五) JSX 绑定属性
【1】固定属性
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const id = 5
const title = '我是title';
const imgSrc = './img/a.png';
const imgAlt = '图片备注';
const element = (
<div>
<div data-id={id}>携带额外的信息</div>
<div title={title}>这里有title</div>
{/*单标签元素必须以 /> 结尾*/}
<img src={imgSrc} alt={imgAlt} />
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
【2】class
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const className = 'active';
const isActive = true;
// 已有的css类上添加新的类
const className2 = `white blue ${isActive ? className : ''}`;
const className3 = ['white', 'blue'];
if (isActive) {
className3.push(className);
}
const element = (
<div>
{/*会被警告: Invalid DOM property `class`*/}
<div class={className}>被警告</div>
{/*正确做法*/}
<div className="abc">普通</div>
<div className={className}>正确</div>
<div className={className2}>字符串</div>
<div className={className3.join(' ')}>数组</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
【3】style
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
const styleObj = {
color: 'blue',
fontWeight: 'bold',
// 'font-size': '22px' // 警告:Unsupported style property font-size
};
const element = (
<div>
{/*报错:The `style` prop expects a mapping from style properties to values, not a string.*/}
{/*<div style="color: blue;">普通</div>*/}
{/* 对象 */}
<div style={{ color: 'red', fontSize: '20px' }}>对象1</div>
<div style={styleObj}>对象2</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
(六) 组件化(类的方式封装组件)
-
使用类方式封装一个组件
- 定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
- 组件必须实现 render 函数并返回的jsx内容,之后React会帮助我们渲染jsx内容
-
参与界面更新的数据我们也可以称之为是
参与数据流
,这个数据是定义在当前对象的state中- 当我们的数据发生变化时,我们可以调用
this.setState
来更新数据,并且通知React 进行 update 操作; - 在进行 update 操作时,就会重新调用
render
函数,并且使用最新的数据,来渲染界面
<div id="root"></div> <!-- 需要引入react文件 --> <script type="text/babel"> // 1.类组件 class App extends React.Component { constructor() { super(); // 页面需要渲染的数据,放在 state 中且名字不可变 this.state = { message: 'Hello World', active: true, styleObj: { color: 'blue', fontSize: '22px', }, }; setTimeout(() => { // 两秒之后更新message, 页面内容将会随着更新 this.setState({ message: 'Hello React', }); }, 2000); } // 必须有, 调用该组件时就会调用该方法 // 数据改变时,该方法将会重新被调用 render() { const { active } = this.state; return ( <div> <h2>{this.state.message}</h2> <p className={active ? 'active' : ''}>一段话</p> <p style={this.state.styleObj}>第二段话</p> </div> ); } } // 将组件添加到页面 const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); </script>
- 当我们的数据发生变化时,我们可以调用
(七) JSX 事件绑定
-
React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
-
通过
{}
传入一个事件处理函数,这个函数会在事件发生时被执行;<div id="root"></div> <!-- 需要引入react文件 --> <script type="text/babel"> class App extends React.Component { constructor() { super(); this.state = { message: 'Hello World', }; } render() { return ( <div> <h2>{this.state.message}</h2> <button onClick={this.btnClick}>按钮</button> </div> ); } btnClick() { console.log(this); // undefined console.log('按钮被执行'); } } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); </script>
【1】事件中 this
-
由上一个案例中可以看到调用的事件中的
this
为undefined
: -
ES6 规定在 JavaScript 类中默认就是严格模式 ,严格模式下找不到 this 的指向将会是 undefined
'use strict'; /** * this 的四种绑定: * 1. 默认绑定 foo() * 2. 隐式绑定:被一个对象执行 obj.foo() -> obj * 3. 显式绑定:call/ apply/ bind * 4. new 绑定:new Foo() -> 创建一个新对象,并且赋值给 this */ class A { btnClick() { console.log(this); } } const a = new A(); a.btnClick(); // A const btnClick2 = a.btnClick; btnClick2(); // undefined function foo() { console.log(this); } // 非严格模式下:window // 严格模式下:undefined foo();
-
在上一个案例中 this 为 undefined 原因: btnClick 函数只是被绑定在button 中(this 没有隐式绑定为当前类),并没有直接调用,当 React 内部调用了btnClick函数时,直接执行了函数(默认绑定),该函数 中的 this 没有被绑定,所以是 undefined;
-
如何解决this的问题呢?
- 方案一:bind给btnClick显示绑定this
- 方案二:使用 ES6 class fields 语法
- 方案三:事件监听时传入箭头函数(个人推荐)
<div id="root"></div> <!-- 需要引入react文件 --> <script type="text/babel"> class App extends React.Component { constructor() { super(); this.state = { message: 'Hello World', count: 1, }; // 解决方案一: this.btn2 = this.increase.bind(this); } render() { const { count } = this.state; return ( <div> <h2> {this.state.message} {count} </h2> {/*this没有指定:*/} <button onClick={this.btnClick}>按钮1</button> {/*解决方案一:*/} <button onClick={this.increase.bind(this)}>添加2</button> <button onClick={this.btn2}>添加2.1</button> {/*解决方案二:*/} <button onClick={this.decrease}>减少3</button> {/*解决方案三:直接传入箭头函数*/} {/*推荐*/} {/*方式一:*/} <button onClick={() => { console.log(this); this.setState({ count: this.state.count - 1 }); }} > 减少4 </button> {/*方式二:*/} {/*可以和按钮1做个对比(隐式绑定this)*/} <button onClick={() => this.btnClick2()}>减少4.1</button> </div> ); } btnClick() { console.log(this); // undefined console.log('按钮被执行'); } increase() { console.log(this); // App this.setState({ count: this.state.count + 1, }); } // 解决方案二:将类属性设置为一个箭头函数 decrease = () => { console.log(this); // App this.setState({ count: this.state.count - 1, }); }; // 解决方案三:通过箭头函数调用该函数,可以和btnClick函数做个对比 // 推荐 btnClick2() { console.log(this); // App this.setState({ count: this.state.count - 1, }); } } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); </script>
【2】事件中参数的传递
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
class App extends React.Component {
constructor() {
super();
this.state = {
message: 'Hello World',
};
}
btnClick(event) {
// 被React 包装过的event:SyntheticBaseEvent
console.log(event);
// 按钮1:undefined
// 按钮2:App
console.log(this);
}
btnClick2(name, age, event) {
console.log(name, age, event);
}
btnClick3(event, name, age) {
console.log(event, name, age);
}
render() {
return (
<div>
{/* 传递event */}
<button onClick={this.btnClick}>按钮1</button>
<button onClick={this.btnClick.bind(this)}>按钮2</button>
<button onClick={(event) => this.btnClick(event)}>按钮3</button>
<br />
{/* 传递额外的参数 */}
{/* 没有显示的传递event */}
{/* 函数 foo('123') 传递的参数,会在 bind 绑定时传递参数的后边 */}
<button onClick={this.btnClick2.bind(this, '张三', 15)}>
按钮4
</button>
<button onClick={(event) => this.btnClick3(event, '里斯', 25)}>
按钮5
</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
(八) 条件判断
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
class App extends React.Component {
constructor() {
super();
this.state = {
message: 'Hello World',
isReady: false,
friend: {
name: '张三',
age: 14,
},
};
}
render() {
let { isReady, friend } = this.state;
//方式一: 使用if语句
let showElement;
if (isReady) {
showElement = <div>我准备好了!</div>;
} else {
showElement = <div>还没有准备好</div>;
}
return (
<div>
{showElement}
{/*方式二:三元运算符*/}
<div>{isReady ? <span>已经好了</span> : <span>没有好</span>}</div>
{/*方式三:&&与运算符*/}
<div>{friend && <span>姓名:{friend.name},年龄:{friend.age}</span> }</div>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
(九) 列表渲染
【1】案例
<div id="root"></div>
<!-- 需要引入react文件 -->
<script type="text/babel">
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{ id: '001', name: '张三', score: 50 },
{ id: '002', name: '里斯', score: 70 },
{ id: '003', name: '王五', score: 80 },
{ id: '004', name: '赵六', score: 90 },
],
};
}
render() {
const { students } = this.state;
const studentInfos = students.map((item, index) => (
<li key={item.id}>
学号:{item.id},姓名:{item.name},成绩:{item.score}
<button
onClick={() => {
this.delStu(index);
}}
>
删除学生
</button>
</li>
));
const betterInfos = students
.filter((item) => item.score > 70)
.map((item) => (
<li key={item.id}>
学号:{item.id},姓名:{item.name},成绩:{item.score}
</li>
));
return (
<div>
<h2>学生信息</h2>
<ul>{studentInfos}</ul>
<h2>成绩大于70的学生</h2>
<ul>{betterInfos}</ul>
</div>
);
}
delStu(index) {
// 不要直接操作 state 中的数据
// 将需要操作的数据进行浅拷贝
const newStudents = [...this.state.students];
newStudents.splice(index, 1);
// 更新数据
this.setState({
students: newStudents,
});
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
【2】列表中的 key
- key主要的作用是为了提高diff算法时的效率;
- 必须给数组中的每一项都指定一个 key——它可以是字符串或数字的形式,只要能唯一标识出各个数组项就行
- 一个精心选择的 key 值所能提供的信息远远不止于这个元素在数组中的位置。即使元素的位置在渲染的过程中发生了改变,它提供的 key 值也能让 React 在整个生命周期中一直认得它。
- 直接把数组项的索引当作 key 值来用,实际上,如果你没有显式地指定 key 值,React 确实默认会这么做。但是数组项的顺序在插入、删除或者重新排序等操作中会发生改变,此时把索引顺序用作 key 值会产生一些微妙且令人困惑的 bug。
- 请不要在运行过程中动态地产生 key,像是 key={Math.random()} 这种方式。这会导致每次重新渲染后的 key 值都不一样,从而使得所有的组件和 DOM 元素每次都要重新创建。这不仅会造成运行变慢的问题,更有可能导致用户输入的丢失。所以,使用能从给定数据中稳定取得的值才是明智的选择。
(十) JSX的原理
【1】React.createElement 函数
-
实际上,jsx 仅仅只是
React.createElement(component, props, ...children)
函数的语法糖。所有的jsx最终都会被转换成React.createElement
的函数调用。 -
createElement需要传递三个参数:
- 参数一:type
- 当前ReactElement的类型;
- 如果是标签元素,那么就使用字符串表示 ,如: “div”;
- 如果是组件元素,那么就直接使用组件的名称;
- 参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储;
- 比如传入className作为元素的class;
- 参数三:children
- 存放在标签中的内容,以children数组的方式进行存储;
- 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
- 参数一:type
-
该 JSX 代码将会被 babel 转换为 React.createElement 函数形式(具体看 通过babel官网将 jsx 进行转换)
<div className="active" id="box"> <span data-id="1" onClick={btnClick}><label>子元素1</label></span> <span data-id="2">{msg}</span> </div>
/*#__PURE__*/ React.createElement( "div", { className: "active", id: "box" }, /*#__PURE__*/ React.createElement( "span", { "data-id": "1", onClick: btnClick }, /*#__PURE__*/ React.createElement("label", null, "\u5B50\u5143\u7D201") ), /*#__PURE__*/ React.createElement( "span", { "data-id": "2" }, msg ) );
【2】通过 babel 官网将 jsx 进行转换
-
babel 官网:https://babeljs.io/
-
在官网中找到 Try it out 选项,并进行如下配置,即可看到
-
通过babel 官网转换过的 JSX 可以直接在 react 中使用
<div id="root"></div> <script src="../js/react.development.js"></script> <script src="../js/react-dom.development.js"></script> <!-- 不需要引用babel文件 --> <!-- script 标签不需要type --> <script> class App extends React.Component { constructor() { super(); this.state = { msg: 'hello', }; } render() { const { msg } = this.state; let btnClick = () => { console.log('btnClick'); }; const element = /*#__PURE__*/ React.createElement( 'div', { className: 'active', id: 'box', }, /*#__PURE__*/ React.createElement( 'span', { 'data-id': '1', onClick: btnClick, }, /*#__PURE__*/ React.createElement( 'label', null, '\u5B50\u5143\u7D201' ) ), /*#__PURE__*/ React.createElement( 'span', { 'data-id': '2', }, msg ) ); return element; } } const root = ReactDOM.createRoot(document.getElementById('root')); // 注意这里的写法 root.render(React.createElement(App, null)); </script>
【3】创建虚拟DOM
React.createElement
最终创建出来一个ReactElement
对象React
利用ReactElement
对象组成了一个JavaScript的对象树,即虚拟DOM(Virtual DOM)- React官方的说法:Virtual DOM 是一种编程理念。
- 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
- 我们可以通过 ReactDOM.render 让虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);
- babel 将 JSX 编译成 React.Component 函数的调用,React.Component 函数执行后将生成虚拟DOM,然后将虚拟DOM渲染到浏览器上
<div id="root"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<!-- 不需要引用babel文件 -->
<!-- script 标签不需要type -->
<script>
class App extends React.Component {
constructor() {
super();
this.state = {
msg: 'hello',
};
}
render() {
const { msg } = this.state;
let btnClick = () => {
console.log('btnClick');
};
const element = /*#__PURE__*/ React.createElement(
'div',
{
className: 'active',
id: 'box',
},
/*#__PURE__*/ React.createElement(
'span',
{
'data-id': '1',
onClick: btnClick,
},
/*#__PURE__*/ React.createElement(
'label',
null,
'\u5B50\u5143\u7D201'
)
),
/*#__PURE__*/ React.createElement(
'span',
{
'data-id': '2',
},
msg
)
);
// 虚拟DOM树
console.log(element);
return element;
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
// 注意这里的写法
root.render(React.createElement(App, null));
</script>