一、JSX语法规则
1.代码
jsx
<!-- 1. 容器 -->
<div id="test"></div>
<!-- 2. 引入依赖库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 3. 编写JSX代码 -->
<script type="text/babel">
// 定义变量
const myId = 'aTgUiGu';
const myData = 'HeLlo,rEaCt';
// 创建虚拟DOM(符合JSX规则)
const VDOM = (
<div>
{/* 小写标签=HTML原生元素,className替代class,{}嵌入JS */}
<h2 className="title" id={myId.toLowerCase()}>
{/* 内联样式:双大括号,{}嵌入JS变量 */}
<span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
</h2>
<h2 className="title" id={myId.toUpperCase()}>
<span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
</h2>
<input type="text"/> {/* 单标签必须闭合 */}
<Good>123</Good> {/* 大写标签=React组件(需提前定义) */}
</div>
);
// 渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'));
</script>
2.知识点
JSX 语法规则
-
定义虚拟 DOM 时,不要写引号。
-
标签中混入 JS 表达式时要用
{}。 -
样式的类名指定不要用
class,要用className。 -
内联样式,要用
style={``{key:value}}的形式去写。 -
只有一个根标签
-
标签必须闭合
-
标签首字母
(1). 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错。
(2). 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。
!CAUTION
补充说明
- 第 2 条的
{}只能嵌入JS 表达式 (变量、运算、函数调用等),不能写语句(如if/for)- 第 4 条的双大括号:外层
{}是 JSX 嵌入表达式的语法,内层{}是样式对象- 第 7 条是 React 区分原生 HTML 标签和自定义组件的核心规则,是组件化开发的基础
二、JSX小练习
1.代码
jsx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 1_JSX渲染列表</title>
</head>
<body>
<!-- 准备好一个"容器" -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 模拟一些数据
const data = ['Angular','React','Vue']
// 1. 创建虚拟DOM
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
// 2. 渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
</html>
2.知识点
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
- 下面这些都是表达式: (1). a (2). a+b (3). demo(1) (4). arr.map() (5). function test () {}
- 下面这些都是语句(代码): (1). if(){} (2). for(){} (3). switch(){case:xxxx}
三、函数式组件
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>1_函数式组件</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1. 创建函数式组件
function MyComponent() {
console.log(this); // 此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
</script>
</body>
</html>
!NOTE
执行了ReactDOM.render(...之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
四、复习类相关的知识1
总结:
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 如果 A 类继承了 B 类,且 A 类中写了构造器,那么 A 类构造器中的
super是必须要调用的。 - 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
五、类式组件
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>2_类式组件</title>
</head>
<body>
<!-- 组件挂载容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1. 创建类式组件
class MyComponent extends React.Component {
render() {
// render是放在哪里的? --- MyComponent的原型对象上,供实例使用。
// render中的this是谁? --- MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:', this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
</script>
</body>
</html>
六、初始化state
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Weather 组件示例</title>
</head>
<body>
<!-- 组件挂载容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1. 创建组件
class Weather extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isHot: false }
}
render() {
console.log(this)
return <h1>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
</body>
</html>
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 类组件 - 解构赋值 State</title>
</head>
<body>
<!-- 容器 -->
<div id="test"></div>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
constructor(props) {
super(props);
// 初始化状态
this.state = { isHot: false };
}
render() {
// 🔥 图中的关键写法:从 state 中解构出 isHot
const { isHot } = this.state;
// 解构后直接使用变量,代码更简洁
return <h1>今天天气很{isHot ? '炎热' : '凉爽'}</h1>;
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'));
</script>
</body>
</html>
七、setState的使用
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 类组件 State 详解</title>
</head>
<body>
<!-- 容器 -->
<div id="test"></div>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
constructor(props) {
console.log('constructor'); // 初始化执行
super(props);
// 初始化状态:包含 isHot 和 wind 两个属性
this.state = { isHot: false, wind: '微风' };
// 🔥 关键:绑定 this,解决 changeWeather 中的 this 指向问题
// 因为作为回调函数调用时,this 会是 undefined
this.changeWeather = this.changeWeather.bind(this);
}
// render 调用次数:1 + n 次
// 1 次是初始化渲染,n 次是 setState 更新状态的次数
render() {
console.log('render'); // 每次状态更新都会执行
// 🔥 解构赋值:从 state 中取出 isHot 和 wind
// 让代码更简洁,不用反复写 this.state
const { isHot, wind } = this.state;
// 绑定点击事件 this.changeWeather
// JSX 中必须用 { } 包裹 JS 表达式
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? '炎热' : '凉爽'}, {wind}
</h1>
);
}
// 自定义方法:切换天气状态
changeWeather() {
// 注释知识点:
// changeWeather 定义在 Weather 的原型对象上,供实例使用
// 作为 onClick 回调,直接调用时类方法默认严格模式下 this 为 undefined
// 所以必须在 constructor 中 bind(this)
console.log('changeWeather');
// 获取原状态
const isHot = this.state.isHot;
// ⚠️ 严重注意:状态必须通过 setState 更新!
// 1. 状态更新是合并(merge)操作,不是替换
// 2. 直接修改 this.state 不会触发视图更新(错误写法)
this.setState({ isHot: !isHot });
// ❌ 错误示范:直接修改 state (不会重新渲染)
// this.state.isHot = !isHot;
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'));
</script>
</body>
</html>
💡 核心知识点总结(对应图片内容)
1. 构造器与 State 初始化
- 多状态管理 :
this.state = { isHot: false, wind: '微风' }可以同时初始化多个状态属性。 super(props):必须调用,继承父类React.Component,并让组件能通过this.props接收参数。
2. this 指向问题(核心难点)
- 现象 :在
changeWeather中直接使用this会是undefined。 - 原因 :
onClick={this.changeWeather}是作为回调函数调用,不是通过实例调用,类方法默认开启严格模式。 - 解决方案 :
this.changeWeather = this.changeWeather.bind(this)将方法的this强制绑定为组件实例。
3. 渲染次数详解
-
constructor:只执行 1 次(组件初始化时)。 -
render:执行 1 + n 次。- 1 次:初始化渲染。
- n 次:每次调用
this.setState修改状态,render 就会重新执行一次。
4. 状态更新的铁律
- 必须用
setState:不能直接写this.state.isHot = true。 - 合并机制 :
setState({ isHot: !isHot })只会修改isHot,不会丢失wind属性(React 会自动合并)。
八、setState的简化使用
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 类组件 State 详解</title>
</head>
<body>
<!-- 容器 -->
<div id="test"></div>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Weather extends React.Component{
//初始化状态
state={isHot:false,wind:'微风'}
reder(){
const{isHot,wind}=this.state
return <h1 onClick={this.chaangeWeather}>今天天气很{isHot?'炎热':'凉爽'} ,{wind}</h1>
}
//自定义方法--要用赋值语句的形式-箭头函数
changeWeatherr=()=>{
const isHot=this.state.isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
</body>
</html>
九、props的基本使用
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 类组件 - Props 基本使用</title>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<!-- 容器1 -->
<div id="test1"></div>
<!-- 容器2 -->
<div id="test2"></div>
<!-- 容器3 -->
<div id="test3"></div>
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render() {
console.log(this);
// 🔥 解构赋值:从 props 中取出 name、age、sex
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
// 渲染组件到页面
// 第一个组件
ReactDOM.render(
<Person name="jerry" age="19" sex="男"/>,
document.getElementById('test1')
);
// 第二个组件
ReactDOM.render(
<Person name="tom" age="18" sex="女"/>,
document.getElementById('test2')
);
// 第三个组件
ReactDOM.render(
<Person name="老刘" age="30" sex="女"/>,
document.getElementById('test3')
);
</script>
</body>
</html>
💡 核心知识点详解(对应图片内容)
1. Props 的基本用法
- 传递数据 :在组件标签上以属性 的形式传递数据,例如
name="jerry"。 - 接收数据 :在组件类中通过
this.props接收,this.props是一个只读对象。 - 解构赋值 :图片中使用了
const {name, age, sex} = this.props,这是开发中最常用的写法,可以简化代码,避免反复书写this.props.xxx。
2. 多次渲染 API (ReactDOM.render)
- 图片中连续调用了三次
ReactDOM.render,分别挂载到了test1、test2、test3三个不同的 DOM 容器上。 - 这说明 React 可以同时向页面的多个不同区域渲染组件,同一个
Person组件类可以生成多个不同数据的实例。
十、批量传递props
1.对一个数组里面传过来的所有数据进行求和
jsx
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript" >
// 数组展开运算符基础用法
let arr1 = [1,3,5,7,9]
let arr2 = [2,4,6,8,10]
console.log(...arr1); //展开一个数组
let arr3 = [...arr1,...arr2] //连接数组
// 剩余参数 + reduce 求和函数
function sum(...numbers){
return numbers.reduce((preValue,currentValue)=>{
return preValue + currentValue
})
}
console.log(sum(1,2,3,4));
</script>
</body>
</html>
!WARNING
展开运算符不能展开对象
jsx
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript" >
// 数组展开运算符基础用法
let arr1 = [1,3,5,7,9]
let arr2 = [2,4,6,8,10]
console.log(...arr1); //展开一个数组
let arr3 = [...arr1,...arr2] //连接数组
//在函数中使用(剩余参数)
function sum(...numbers){
return numbers.reduce((preValue,currentValue)=>{
return preValue + currentValue
})
}
console.log(sum(1,2,3,4));
//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
let person2 = {...person}
//console.log(...person); //报错,展开运算符不能展开对象
person.name = 'jerry'
console.log(person2);
console.log(person);
//合并对象
let person3 = {...person,name:'jack',address:"地球"}
console.log(person3);
</script>
</body>
</html>
代码核心知识点拆解
1. 数组展开运算符
console.log(...arr1):将数组[1,3,5,7,9]展开为独立参数1 3 5 7 9打印let arr3 = [...arr1,...arr2]:合并两个数组,等价于arr1.concat(arr2),是 ES6 推荐写法
2. 函数剩余参数(...numbers)
- 函数
sum(...numbers)会把所有传入的参数(1,2,3,4)收集为一个数组[1,2,3,4] - 配合
Array.prototype.reduce实现累加求和,最终返回1+2+3+4 = 10
3. 对象展开运算符
let person2 = {...person}:浅拷贝 对象,修改原对象person不会影响拷贝后的person2- 注意:
console.log(...person)会报错,因为展开运算符不能直接展开普通对象(仅支持可迭代对象,数组 / 类数组) let person3 = {...person,name:'jack',address:"地球"}:合并对象,后写的属性会覆盖前面的同名属性(这里name被覆盖为jack,新增address属性)
补充说明
- 对象展开是 ES2018(ES9)新增特性,仅支持对象字面量中使用
- 展开运算符做的是浅拷贝,如果对象有嵌套属性,嵌套层仍会共享引用
- 合并对象时,属性顺序很重要:后面的属性会覆盖前面的同名属性
2.批量传递props
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React Props 展开运算符</title>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
console.log(this);
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
// 用对象 + 展开运算符传递 props
const p = { name: '老刘', age: 18, sex: '女' };
console.log('@', ...p);
// 等价写法:<Person name={p.name} age={p.age} sex={p.sex}/>
ReactDOM.render(
<Person {...p}/>,
document.getElementById('test3')
);
</script>
</body>
</html>
核心知识点拆解
1. 展开运算符传递 Props(核心亮点)
-
传统写法:需要逐个手动传递属性
jsx<Person name={p.name} age={p.age} sex={p.sex}/> -
简化写法:用
{...p}一次性把对象p的所有属性作为 props 传递给组件jsx<Person {...p}/> -
效果完全一致,代码更简洁,适合 props 较多的场景
2. 关键细节
console.log('@', ...p)会报错(对象不能直接展开打印),仅数组 / 可迭代对象支持直接展开{...p}是对象展开语法,仅在 JSX 组件标签中生效,用于批量传递 props- 展开后 props 是只读的,组件内不能修改
this.props,只能通过父组件重新传递更新
补充说明
-
这是 React 开发中批量传递 props 的标准写法,在封装通用组件、列表渲染时非常常用
-
可以在展开后追加 / 覆盖属性,例如:
jsx<Person {...p} name="新名字" age={20}/>后写的属性会覆盖展开对象中的同名属性
十一、对props进行限制
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 组件属性校验与默认值</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 🔥 必须引入 PropTypes 库 (React 15.5+ 后独立出来) -->
<script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
<!-- 容器 -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
// 1. 创建类式组件
class Person extends React.Component {
render() {
console.log(this);
const { name, age, sex, speak } = this.props;
return (
<ul>
{/* 展示数据 */}
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li> {/* 示例:展示时可以做简单计算 */}
</ul>
);
}
}
// 2. 🔥 对标签属性进行类型、必要性的限制 (PropTypes)
// 这是开发环境下的校验,生产环境通常会关闭或移除
Person.propTypes = {
//name:必须是字符串,且必填(isRequired)
name:PropTypes.string.isRequired
//sex:限制为字符串
sex:PropTypes.string
//age:限制为数值
age:PropTypes.number
//speak:限制为函数
speak:PropTypes.func
}
// 3. 🔥 指定默认标签属性值 (defaultProps)
// 如果父组件没有传递该属性,就使用这里的默认值
Person.defaultProps = {
sex:'男' //默认性别为男性
age:18 //默认年龄为18岁
}
// 4. 渲染组件到页面
// test1: 只传递必填项 name,sex 和 age 会走默认值,speak 是函数
ReactDOM.render(
<Person name="jerry" speak={() => {console.log('说话')}} />,
document.getElementById('test1')
);
// test2: 覆盖默认值
ReactDOM.render(
<Person name="tom" age={18} sex="女"/>,
document.getElementById('test2')
);
// 5. 展开运算符传递 props
const p = { name: '老刘', age: 18, sex: '女' };
// 等价于 <Person name="老刘" age={18} sex="女"/>
ReactDOM.render(
<Person {...p}/>,
document.getElementById('test3')
);
</script>
</body>
</html>
十二、props的简写方式
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 类内 static 属性校验</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入 PropTypes 库 -->
<script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
// 创建组件
class Person extends React.Component {
// 🔥 类内 static 写法:对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 🔥 类内 static 写法:指定默认标签属性值
static defaultProps = {
sex: '男', // sex默认值为男
age: 18 // age默认值为18
}
render() {
const { name, age, sex, speak } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
// 定义 speak 函数(用于传递给组件)
const speak = () => {
console.log('我会说话');
};
// 渲染组件到页面
ReactDOM.render(
<Person name="jerry" speak={speak}/>,
document.getElementById('test1')
);
ReactDOM.render(
<Person name="tom" age={18} sex="女"/>,
document.getElementById('test2')
);
const p = { name: '老刘', age: 18, sex: '女' };
// console.log('@', ...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>, document.getElementById('test3'))
ReactDOM.render(
<Person {...p}/>,
document.getElementById('test3')
);
</script>
</body>
</html>
十三、函数式组件使用props
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 函数式组件 Props 校验</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入 PropTypes 校验库 -->
<script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 1. 定义函数式组件
function Person(props) {
// 解构 props 中的属性
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
// 2. 对 props 进行类型、必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制 name 必传,且为字符串
sex: PropTypes.string, // 限制 sex 为字符串
age: PropTypes.number // 限制 age 为数值
};
// 3. 指定默认 props 值
Person.defaultProps = {
sex: '男', // sex 默认值为男
age: 18 // age 默认值为 18
};
// 4. 渲染组件到页面
ReactDOM.render(
<Person name={100}/>,
document.getElementById('test1')
);
</script>
</body>
</html>
十四、字符串形式的ref
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 字符串形式 refs</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
// 展示左侧输入框的数据
showData = () => {
const { input1 } = this.refs
alert(input1.value)
}
// 展示右侧输入框的数据
showData2 = () => {
const { input2 } = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
</script>
</body>
</html>
十五、回调形式的ref
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 回调形式 refs</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
// 展示左侧输入框的数据
showData = () => {
const { input1 } = this
alert(input1.value)
}
// 展示右侧输入框的数据
showData2 = () => {
const { input2 } = this
alert(input2.value)
}
render() {
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
</script>
</body>
</html>
!IMPORTANT
调用次数:初始1次,更新2次
十六、createRef的使用
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React createRef 形式 refs</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是"专人专用"的
*/
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框的数据
showData = () => {
alert(this.myRef.current.value);
}
// 展示右侧输入框的数据
showData2 = () => {
alert(this.myRef2.current.value);
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
</script>
</body>
</html>
十七、react中的事件处理
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 事件处理</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
/*
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件 ------ 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ------为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ------------------不要过度使用ref
*/
// 创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框的数据
showData = (event) => {
console.log(event.target);
alert(this.myRef.current.value);
}
// 展示右侧输入框的数据
showData2 = (event) => {
alert(event.target.value);
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
</body>
</html>
十八、非受控组件
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 表单登录组件</title>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Login extends React.Component {
handleSubmit = (event) => {
// 阻止表单默认提交行为(避免页面刷新)
event.preventDefault()
const { username, password } = this
alert(`你输入的用户名是: ${username.value},你输入的密码是: ${password.value}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名: <input ref={c => this.username = c} type="text" name="username"/>
密码: <input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
</body>
</html>
十九、受控组件
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 受控组件登录</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Login extends React.Component {
// 初始化状态
state = {
username: '', // 用户名
password: '' // 密码
}
// 保存用户名到状态中
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
// 保存密码到状态中
savePassword = (event) => {
this.setState({ password: event.target.value })
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名: <input onChange={this.saveUsername} type="text" name="username"/>
密码: <input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
</body>
</html>
核心知识点拆解
1. 什么是受控组件?
- 定义 :表单元素的值由 React 的
state完全控制,输入框的value绑定state,通过onChange事件同步更新state。 - 对比非受控组件 :非受控组件用
ref直接操作 DOM 获取值;受控组件用state管理,更符合 React 数据驱动的思想,方便做校验、联动等复杂逻辑。
2. 代码执行流程
- 初始化 :
state中username和password初始为空字符串。 - 输入同步 :用户在输入框输入内容时,触发
onChange事件,执行saveUsername/savePassword,将输入值更新到state。 - 提交处理 :点击登录按钮,触发表单
onSubmit事件,先调用event.preventDefault()阻止页面刷新,再从state中取出用户名和密码,弹窗展示。
二十、高阶函数和函数的柯里化
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 高阶函数 柯里化 登录表单</title>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
//#region
/*
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
*/
//#endregion
// 创建组件
class Login extends React.Component {
// 初始化状态
state = {
username: '', // 用户名
password: '' // 密码
}
// 保存表单数据到状态中(柯里化写法)
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
密码: <input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
</body>
</html>
二十一、不用柯里化的写法
jsx
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React 表单 柯里化 vs 非柯里化</title>
<!-- 引入核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
// 创建组件
class Login extends React.Component {
// 初始化状态
state = {
username: '', // 用户名
password: '' // 密码
}
// ================== 写法1:非柯里化(箭头函数传参) ==================
// 保存表单数据到状态中(直接接收两个参数)
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value })
}
// ================== 写法2:柯里化(函数返回函数) ==================
// saveFormData = (dataType) => {
// return (event) => {
// this.setState({ [dataType]: event.target.value })
// }
// }
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
{/* 写法1:非柯里化,用箭头函数在onChange中传参 */}
用户名: <input onChange={event => this.saveFormData('username', event)} type="text" name="username"/>
密码: <input onChange={event => this.saveFormData('password', event)} type="password" name="password"/>
{/* 写法2:柯里化,直接传字段名,无需额外箭头函数 */}
{/* 用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
密码: <input onChange={this.saveFormData('password')} type="password" name="password"/> */}
<button>登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
</body>
</html>
二十二、生命周期钩子

React 组件生命周期(旧版)
1. 初始化阶段(由 ReactDOM.render () 触发 --- 初次渲染)
-
constructor() -
componentWillMount() -
render() -
componentDidMount()常用- 一般在这里做初始化操作:开启定时器、发送网络请求、订阅消息
2. 更新阶段(由组件内部 this.setState() 或父组件 render 触发)
shouldComponentUpdate()componentWillUpdate()render()必须使用componentDidUpdate()
3. 卸载组件(由 ReactDOM.unmountComponentAtNode() 触发)
componentWillUnmount()常用- 一般在这里做收尾工作:关闭定时器、取消订阅消息
!IMPORTANT
三、新旧生命周期核心区别(必背)
1. 被废弃的 3 个钩子(旧版有,新版不能用)
componentWillMountcomponentWillReceivePropscomponentWillUpdate2. 新增的 2 个钩子(新版才有)
static getDerivedStateFromProps(props, state)getSnapshotBeforeUpdate(prevProps, prevState)

二十三、static getDerivedStateFromProps(props, state)
核心特点(必须记住)
-
是静态方法 → 前面必须加
static -
不能用 this → 里面不能写
this.setState、this.xxx -
必须有返回值
- 返回一个对象 → 表示要更新 state
- 返回 null → 表示不更新
-
在每次渲染前都会执行
- 挂载时执行
- 更新时执行(state 变、props 变都会触发)
二十四、getSnapshotBeforeUpdate(prevProps, prevState)
核心特点(必背)
-
执行时机 :在
render()之后、DOM 真正更新到页面之前执行- 此时你能拿到更新前的 props/state/DOM,但 DOM 还没被新数据覆盖
-
参数 :
prevProps(更新前的 props)、prevState(更新前的 state) -
必须有返回值:
- 返回任意类型的值(对象、数字、DOM 信息等),这个值会作为第 3 个参数
snapshot传给componentDidUpdate(prevProps, prevState, snapshot) - 如果不需要快照,返回
null即可
- 返回任意类型的值(对象、数字、DOM 信息等),这个值会作为第 3 个参数
-
不能调用
setState:它是纯函数,只能计算快照,不能触发新的更新 -
必须配合
componentDidUpdate使用 :快照的价值要在componentDidUpdate中体现
二十五、新版生命周期钩子
1. 初始化阶段(由 ReactDOM.render() 触发 ------ 初次渲染)
-
constructor() -
static getDerivedStateFromProps(props, state) -
render() -
componentDidMount()(常用)- 一般在这个钩子中做初始化操作,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段(由组件内部 this.setState() 或父组件重新 render 触发)
static getDerivedStateFromProps(props, state)shouldComponentUpdate(nextProps, nextState)render()getSnapshotBeforeUpdate(prevProps, prevState)componentDidUpdate(prevProps, prevState, snapshot)
3. 卸载组件(由 ReactDOM.unmountComponentAtNode() 触发)
-
componentWillUnmount()(常用)- 一般在这个钩子中做收尾工作,例如:关闭定时器、取消订阅消息
补充说明
getDerivedStateFromProps:静态方法,用于根据最新props同步state,替代了旧版的componentWillMount/componentWillReceivePropsgetSnapshotBeforeUpdate:在 DOM 更新前捕获快照,传递给componentDidUpdate,替代了旧版的componentWillUpdaterender:唯一必须实现的钩子,用于返回 JSX,生成虚拟 DOMcomponentDidMount/componentWillUnmount:新旧版本通用,分别负责组件挂载后的初始化和卸载前的清理
二十六、diff算法
1. 虚拟 DOM 中 key 的作用
1.1 简单定义
key 是虚拟 DOM 对象的唯一标识 ,在 React 更新界面时起到核心定位作用。
1.2 详细 Diff 比较规则
当数据变化触发更新时,React 生成新虚拟 DOM ,与旧虚拟 DOM 进行 Diff 对比,规则如下:
-
a. 找到相同 key
- (1) 若虚拟 DOM 内容未变 → 直接复用之前的真实 DOM(不重新渲染)。
- (2) 若虚拟 DOM 内容变了 → 生成新的真实 DOM,替换掉旧的。
-
b. 未找到相同 key
- 根据新数据直接创建新的真实 DOM,渲染到页面。
2. 为什么遍历列表时,key 最好不要用 index?
2.1 问题一:性能浪费(逆序操作)
如果进行逆序添加、逆序删除等破坏顺序的操作:
- 现象 :React 会误以为所有元素的内容都变了,导致产生大量不必要的真实 DOM 更新。
- 结果 :界面显示正常,但执行效率极低。
2.2 问题二:界面 Bug(含输入类 DOM)
如果列表结构中包含 <input> 等输入类 DOM:
- 现象 :利用
index作为 key,移动 / 增删数据时,输入框的状态会错位(张冠李戴)。 - 结果 :产生错误的 DOM 更新,导致界面逻辑完全错误。
2.3 特殊情况(例外)
如果不存在逆序操作,且数据仅用于纯展示(没有增删改):
- 使用
index作为 key 是完全没问题的。
3. 开发中如何选择 key?(最佳实践)
3.1 首选:唯一标识
必须使用每条数据的唯一身份标识,例如:
id(数据库主键)- 手机号、身份证号、学号等唯一不重复的值。
3.2 次选:简单展示
如果确定只是静态展示数据(不会进行任何增删改排序操作):
- 可以使用
index作为 key。