# 概述
也是单页面应用 (spa),public 下只有一个 index.html,也是数据驱动视图,也是虚拟 dom
React 是一个将数据渲染为 HTML 视图的开源 JavaScript 库 (只 关注于视图 ),是 Facebook 开发的
# 为什么要学
- 原生 JavaScript 操作 DOM 繁琐,
效率低 - 使用 JavaScript 直接操作 DOM,浏览器会进行大量的
重绘重排 - 原生 Javascript 没有
组件化编码方案,代码复用率低下
# React 特点
- 采用
组件化模式,声明式编码,提高开发效率及组件复用率 - 在
React Native(可以让前端人员通过 js 去编写 Android 和 ios 的一门技术) 中可以使用React 语法进行移动端开发 - 使用
虚拟 DOM和优秀的Diff 算法,尽量减少与真实 DOM 的交互
# hello_react 案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>hello_react</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/* 此处一定要写babel,将jsx通过babel转换为js */
/* 不写默认是js */
//1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1>;
/* 此处一定不要写引号,因为不是字符串 */
const VDOM2 = <h1>Hello,Mrr</h1>;
//2.渲染虚拟DOM到页面
/* render(虚拟DOM,容器) */
// ReactDOM 是因为引入了react-dom.development.js ,React是因为引入了react.development.js
// 注意这里 ReactDOM 的写法
ReactDOM.render(VDOM, document.getElementById("test"));
ReactDOM.render(VDOM2, document.getElementById("test")); //这里会将上边的替换掉
</script>
</body>
</html>
# 虚拟 dom 的两种创建方式
- 有了
jsx可以使编码人员更简单的创建虚拟 dom jsx里边的这种写法创建虚拟 dom,其实就是原始 js创建虚拟 dom 这种写法的一个语法糖(简写的方式)虚拟 Dom本质是一个Object类型的对象 (一般对象)虚拟 DOM比较"轻",真实 Dom比较"重",因为虚拟 Dom是React 内部在用,无需真实 Dom 上那么多的属性虚拟 Dom最终会被React转换为真实 Dom,呈现在页面上debugger断点,可以去控制台中的Sources查看具体内容
-
使用 jsx 创建虚拟 dom
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>hello_react</title> </head> <body> <div id="test"></div> <script src="../js/react.development.js"></script> <script src="../js/react-dom.development.js"></script> <script src="../js/babel.min.js"></script> <script type="text/babel"> /* 用一个 () 表示是一个整体 */ const VDOM = ( <h1 id="title"> <span>Hello,React</span> </h1> ); /* 最终浏览器运行的还是通过babel编译成使用js创建时的样子 */ /* 但写起来很方便 */ ReactDOM.render(VDOM, document.getElementById("test")); </script> </body> </html> -
使用 js 创建虚拟 dom (肯定不用)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>hello_react</title> </head> <body> <div id="test"></div> <script src="../js/react.development.js"></script> <script src="../js/react-dom.development.js"></script> <script> const VDOM = React.createElement( "h1", { id: "title" }, React.createElement("span", {}, "Hello,React") ); ReactDOM.render(VDOM, document.getElementById("test")); </script> </body> </html>
# jsx 语法
- 定义虚拟 DOM 时,
不要写引号。 - 标签中混入 JS 表达式时 (html 中写 js) 要用
{}。注意是表达式! - 标签上样式的类名不要用 class,要用
className。 内联样式,要用style={{key:'value'}}的形式去写。font-size这种的要写成小驼峰形式只有一个根标签标签必须闭合- 标签的 for 换成
htmlFor(比如 label 标签的 for) 标签首字母
(1).若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。jsx 事件绑定`onClick (大写的)= 函数名 (不是函数调用)
# jsx 小练习
一定注意区分: js 语句(代码) 与 js 表达式
const x = 表达式 ( 能用变量接收的就是表达式 )
-
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {} -
语句 (代码):
下面这些都是语句 (代码):
(1).if(){}
(2).for(){}
(3).switch() -
案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>jsx小练习</title> </head> <body> <div id="test"></div> <script src="../js/react.development.js"></script> <script src="../js/react-dom.development.js"></script> <script src="../js/babel.min.js"></script> <script type="text/babel"> //模拟一些数据 const data = ["Angular", "React", "Vue"]; const VDOM = ( <div> <h1>前端js框架列表</h1> <ul> {data.map((item, index) => { /* 不建议写 index */ return <li key={index}>{item}</li>; })} </ul> </div> ); ReactDOM.render(VDOM, document.getElementById("test")); </script> </body> </html>
# 模块与组件、模块化与组件化的理解
-
模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用 js, 简化 js 的编写,提高 js 运行效率
-
组件
- 理解:用来实现局部功能效果的代码和资源的集合 (html/css/js/image 等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码,简化项目编码,提高运行效率
-
模块化
当应用的 js 都以模块来编写的,这个应用就是一个模块化的应用 -
组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
# 组件
组件实例三大属性: state,props,refs
函数式组件 只能用 props , 而 class 类组件才有三大属性
context 也需了解
# 函数式组件
一个函数只能返回一个 html 根节点, 没有生命周期和 state
<script type="text/babel">
// 1.创建函数式组件
// 函数式组件,函数名就是组件名
function MyComponent() {
console.log(this);
// 此处的this是 undefined 而不是 window
// 因为babel编译后开启了严格模式(禁止自定义函数中的this指向window)
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
// 函数必须有返回值
}
// 2.渲染组件到页面
// 组件名首字母大写且必须为闭合标签!!!!
// 这里必须是标签,不能直接写名字
ReactDOM.render(<MyComponent />, document.getElementById("test"));
// 执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
// 1.React解析组件标签,找到了MyComponent组件。如果找不到会报错
// 2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
</script>
# class 类组件
有生命周期和 state
<script type="text/babel">
// 1.创建类式组件
// 类名就是组件名
// React 中规定如果要用类式组件必须要继承React中内置的一个类
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出来该类(MyComponent)的实例,并通过该实例调用到原型上的render方法。
// 是 React 自己做的 new 操作
// 3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
</script>
# 复习类的概念
# 总结
- 类中的构造器
不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。 - 如果 A 类继承了 B 类,且 A 类中写了构造器,那么
A类构造器中的super是必须要调用的。 - 类中所定义的方法,都放在了
类的原型对象上,供实例去使用。
# 案例
//创建一个Person类
class Person {
//构造器方法
constructor(name, age) {
//构造器中的this是谁?—— 类的实例对象,new的时候执行constructor
this.name = name;
this.age = age;
}
//一般方法
speak() {
// speak方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是${this.age}`);
}
}
//创建一个Student类,继承于Person类
class Student extends Person {
constructor(name, age, grade) {
// 只要这里用了构造器就必须用 super
// super 是帮你调用父类的构造器,且 super 必须放在所有人之前
super(name, age);
this.grade = grade;
this.school = "清北";
// 所有由 Student 缔造的实例身上都有一个值为清北的school属性
}
//重写从父类继承过来的方法
speak() {
console.log(
`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`
);
this.study();
}
study() {
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Student实例调用study时,study中的this就是Student实例
console.log("我很努力的学习");
}
}
class Car {
constructor(name, price) {
this.name = name;
this.price = price;
// this.wheel = 4
}
//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个名为a,值为1的属性
a = 1;
wheel = 4;
// 不写 static 是加给了类的实例对象,写的话就是加给了类本身
static demo = 100;
}
const c1 = new Car("奔驰c63", 199);
console.log(c1); // Car {a: 1, wheel: 4, name: "奔驰c63", price: 199}
console.log(Car.demo); // 100
# 组件实例三大属性之一 state 和事件处理
# 案例一
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次,因为在ReactDom.render中只写了一次
constructor(props) {
super(props);
//初始化状态
// 这里是往组件实例中放东西
this.state = { isHot: false, wind: "微风" };
//解决changeWeather中this指向问题
//如果没有这行代码,changeWeather 会指向 undefined
this.changeWeather = this.changeWeather.bind(this);
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
//读取状态
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather() {
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式!!!!,所以changeWeather中的this为undefined
//获取原来的isHot值
const isHot = this.state.isHot;
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({ isHot: !isHot });
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
# 案例一精简版
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//初始化状态
state = { isHot: false, wind: "微风" };
render() {
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? "炎热" : "凉爽"},{wind}
</h1>
);
}
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = () => {
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
};
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
# 案例二 (脚手架)
import { Component } from "react";
class Count extends Component {
state = { count: 100 };
render() {
const { count } = this.state;
return (
<div>
{/* jsx 事件绑定 onClick(大写的)=函数(不是函数调用) */}
<button onClick={this.add}>+</button>
<span>{count}</span>
<button onClick={this.reduce}>-</button>
{/* 事件传参,要用这种方式 */}
<button onClick={() => this.change(500)}>随意修改</button>
</div>
);
}
add = () => {
let { count } = this.state;
this.setState({
// count: ++ this.state.count // 这样会报错,不能直接修改 state
count: count + 1, // 下边的也可以
// count: ++count,
});
};
reduce = () => {
let { count } = this.state;
this.setState({
count: --count,
});
};
change = (newCount) => {
this.setState({
count: newCount,
});
};
}
export default Count;
# state 总结
- 1. 组件中 render 方法中的 this 为
组件实例对象 - 2. 组件自定义的方法中 this 为
undefined,如何解决?- 强制绑定 this: 通过函数对象的
bind() 箭头函数
- 强制绑定 this: 通过函数对象的
- 3.
状态数据,不能直接修改或更新,必须使用setState()
# 组件实例三大属性之一 props
props 是只读的,不可以修改
props 的类型检查,需要借助 prop-types 包实现
(https://react.docschina.org/docs/typechecking-with-proptypes.html)
# 案例一 (脚手架)
// 导入 prop 类型检查包
// prop-types 是 react 自带的
import PropTypes from "prop-types";
import React, { Component } from "react";
class MyButton extends Component {
render() {
const { text = "按钮" } = this.props;
this.props.fun && this.props.fun(); //132456
return <button>{text}</button>;
}
}
// 对组件的 text props 进行类型校验
// 这里是 propTypes
MyButton.propTypes = {
// 这里是 PropTypes
text: PropTypes.string, // 只能传字符串
};
export default MyButton;
# 案例二 (对 props 进行限制)
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script src="../js/prop-types.js"></script>
<!-- 引入了这个文件,就多了一个全局对象 PropTypes -->
<script type="text/babel">
class Person extends React.Component {
render() {
const { name, age, sex } = this.props;
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
// 对标签属性进行类型、必要性的限制
// 给Person类加上propTypes这个属性,就意味着加规则
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
speak: PropTypes.func, //限制speak为函数
};
// 指定默认标签属性值
Person.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
// 渲染组件到页面
ReactDOM.render(
<Person name={100} speak={speak} />,
document.getElementById("test1")
);
ReactDOM.render(
<Person name="tom" age={18} sex="女" />,
document.getElementById("test2")
);
const p = { name: "老刘", age: 18, sex: "女" };
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
function speak() {
console.log("我说话了");
}
</script>
# 案例三 (props 简写方式 (案例二简写方式))
<script type="text/babel">
//创建组件
class Person extends React.Component {
// 在实际开发中基本不写构造器
constructor(props) {
// 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
super(props);
console.log("constructor", this.props);
}
static propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
static defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
ReactDOM.render(<Person name="jerry" />, document.getElementById("test1"));
</script>
# 案例四 (函数式组件使用 props)
函数式组件没法用 state 和 refs
<script src="../js/prop-types.js"></script>
<script type="text/babel">
function Person(props) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
Person.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
ReactDOM.render(<Person name="jerry" />, document.getElementById("test1"));
</script>
# 案例五 (脚手架)
-
父组件传递给子组件内容
-
App.js
import "./App.css"; import MyButton from "./components/MyButton"; import MyTitle from "./components/MyTitle"; function fun() { console.log("132456"); } function App() { return ( <div className="App"> {/* 使用 props 给组件传递内容 */} {/* 传递的名叫fun,值是上边定义的 */} <MyButton text="按钮a" fun={fun} /> {/* 这里的Mybutton调用的是对应组件中render方法的return */} {/* 这里调用Mybutton的时候,会输出132456 */} <MyTitle title="标题a" /> </div> ); } export default App; -
(类组件如何接收 props) MyButton.js
import React, { Component } from "react"; class MyButton extends Component { // render函数和生命周期不需要写成箭头函数也可以获取 this render() { // 子组件接收父组件传递的 props // 父组件传递过来的 props 会自动放在组件的 this 下的 props 属性内 const { text = "按钮" } = this.props; // 设置默认值 // const { text: xxx } = this.props; 将 text 重命名为xxx this.props.fun && this.props.fun(); //132456 return <button>{text}</button>; } } export default MyButton; -
(函数式组件怎么接收 props) MtTitle.js
const MyTitle = (props) => { // 函数组件里没有 this // 函数的参数就是 props const { title = "标题" } = props; return <h3>{title}</h3>; }; export default MyTitle;
-
# 特殊的 prop:children
父组件将内容写在子组件的标签中间传递过来的话,子组件使用 children 接收
当父组件传递的 children 是多个的话,children 就是一个 数组
那么如何区分我到底该使用数组里面的哪一项呢
可以选择父组件传递的时候给 className
然后 className 会被当作其中一项的 props 对象的属性
# 组件实例三大属性之一 refs 和事件处理
不能在 函数组件 上使用 ref 属性
切勿过度使用 ref !!!
# 字符串形式的 ref (官方已废弃)
字符串形式的 ref 如果写多了, 效率就会变得低下 ,所以官方不推荐使用
<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 />, document.getElementById("test"));
</script>
# 回调函数形式的 ref
React 会自动调用 ref 函数,且会将 ref 当前所处的节点当作第一个参数传进去
-
案例一
<script type="text/babel"> //创建组件 class Demo extends React.Component { //展示左侧输入框的数据 showData = () => { // 这里就不是从 this.refs 上取了 // 而是从 this 上边取,因为底下赋值给了 this const { input1 } = this; alert(input1.value); }; //展示右侧输入框的数据 showData2 = () => { const { input2 } = this; alert(input2.value); }; render() { return ( <div> <input ref={(ele) => (this.input1 = ele)} type="text" placeholder="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={(ele) => (this.input2 = ele)} type="text" placeholder="失去焦点提示数据" /> </div> ); } } //渲染组件到页面 ReactDOM.render(<Demo />, document.getElementById("test")); </script> -
案例二 (脚手架)
import { Component } from "react"; class RefsDemo1 extends Component { inputRef = null; render() { return ( <div> {/*该函数默认接收的第一个参数就是真实 dom,将这个真实 dom 赋值给类的属性即可 */} <input type="text" ref={(ele) => (this.inputRef = ele)} /> </div> ); } componentDidMount() { this.inputRef.focus(); } } export default RefsDemo1;
# createRef 形式的 ref (最推荐的)
-
案例一
<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" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" /> </div> ); } } //渲染组件到页面 ReactDOM.render(<Demo />, document.getElementById("test")); </script> -
案例二 (脚手架)
import { Component, createRef } from "react"; class RefsDemo extends Component { // refs 如何使用 // 1. 类里面内定义一个变量,属性的值是 creatRef() // 2. 将这个值绑定到任一元素的 ref 属性上 // 那么当你访问这个属性下的 current 属性时,就访问到了对应的真实 dom p = createRef(); h = createRef(); inputRef = createRef(); state = { count: 10 }; // 利用 ref 实现输入框自动获得焦点 render() { const { count } = this.state; return ( <div> <h3 ref={this.h}>测试refs</h3> <button onClick={this.add}>+1</button> <button onClick={this.getP}>使用 refs 获取段落的真实 dom</button> <p ref={this.p}>我是一个段落{count}</p> <input type="text" ref={this.inputRef} /> </div> ); } // 一上来就获取焦点 componentDidMount() { this.inputRef.current.focus(); // 利用 ref 实现输入框自动获得焦点 } getP = () => { console.log(this.p.current); console.log(this.h.current); }; add = () => { const { count } = this.state; this.setState({ count: count + 1 }); }; } export default RefsDemo;
# 事件处理
-
(1). 通过
onXxx属性指定事件处理函数 (注意大小写)- React 使用的是
自定义(合成)事件,而不是使用的原生 DOM 事件 ->为了更好的兼容性 - React 中的事件是通过
事件委托方式处理的 (委托给组件最外层的元素) ->为了高效 - 里边传递
一定要是一个函数!!!, 跟加不加小括号没有关系saveFormData = (dataType) => { return (event) => { this.setState({ [dataType]: event.target.value }); }; }; <input onChange={this.saveFormData("username")} name="username" />;
- React 使用的是
-
(2). 通过
event.target得到发生事件的 DOM 元素对象 -> 不要过度使用 ref<script type="text/babel"> class Demo extends React.Component { showData2 = (event) => { alert(event.target.value); }; render() { return ( <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" /> ); } } ReactDOM.render(<Demo />, document.getElementById("test")); </script>