前言:
React 的最大特点是采用了组件化的编程模式。开发者可以将页面拆分成多个组件,每个组件有自己的状态和生命周期,并且组件之间可以进行传值、嵌套等操作,从而更加方便地管理和维护页面的代码。此外,React 还支持虚拟 DOM 技术,通过将真实的 DOM 操作转换为虚拟 DOM 操作,实现高效的页面渲染和更新,提升了页面性能。
React 的优点还包括:
- 易学易用:由于 React 的编程模式和语法简单清晰,上手较为容易。
- 可组合性强:React 的组件化编程模式允许开发者把不同的组件拼接在一起以实现不同的功能。
- 高性能:React 的虚拟 DOM 技术能够有效减少浏览器的 DOM 操作,提升页面性能。
- 大量的生态系统和社区支持:React 生态系统庞大,社区活跃,同时拥有许多优秀的衍生产品和插件,能够满足不同场景下的需求。
- 可扩展性强:React 的设计理念可以让开发者方便扩展和定制,可以与其他技术栈进行无缝连接。
简介:
React是一个将数据渲染为HTML视图的开源JavaScript 库。
1.原生JavaScript操作DOM繁琐、效率低(
DOM-API操作 UI
)
- document .getElementById( ‘app’)
- document.querySelector(‘#app’)
- document .getElementsByTagName( ‘ span’)
2.使用JavaScript直接操作DOM,浏览器会进行大量的
重绘重排
3.原生JavaScript没有
组件化
编码方案,代码复用率低
React的特点
1.采用
组件化
模式、
声明式编码
,提高开发效率及组件复用率
2.在
React Native
中可以使用React语法进行
移动端开发
3.使用
虚拟DOM
+优秀的
Diffing 算法
,尽量减少与真实DOM的交互
学习React之前你要掌握的JavaScript基础知识
- 判断this的指向
- class(类)
- ES6语法规范
- npm包管理器
- 原型、原型链
- 数组常用方法
- 模块化
React基本使用
React 高效的原因
(1) 使用虚拟(virtual)DOM, 不总是直接操作页面真实 DOM。
(2) DOM Diffing 算法, 最小化页面重绘。
<!--准备好一个“容器” -->
<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,用于将isx转为is -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM =<h1><span>Hello,React</span></h1>
/* 此处一定不要写引号,因为不是字符串 */
//2.渲染虚拟DOM到页面
ReactDoM.render(VDOM, document.getElementById('test'))
相关js库
(1) react.js: React 核心库。
(2) react-dom.js: 提供操作 DOM的react 扩展库。
(3)babel.min.js: 解JSX 语法代码转为JS代码的库。
<!--准备好一个“容器” -->
<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,用于将isx转为is -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript">
//1.创建虚拟DOM
//const VDOM = React.createElement('h1,{id;'title'],'Hello,React')
//const VDOM = React.createElement(标签名,标签属性,标签内容)
const VDOM = React.createElement('h1',(id:'title'],React.createElement('span',(), 'Hello,React'))
/* 此处一定不要写引号,因为不是字符串 */
//2.渲染虚拟DOM到页面
ReactDoM.render(VDOM, document.getElementById('test'))
</script>
创建虚拟 DOM 的两种方式
(1) 纯JS 方式(一般不用)
(2)JSX方式
关于虚拟DOM:
1.本质是object类型的对象 (一般对象)
2.虚拟DOM属性比较少,真实DOM比较多,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
JSX语法规则
(1)全称: JavaScript XML
(2)react 定义的一种类似于XML的JS 扩展语法:JS+XML
(3)本质是 React.createElement(component, props,…children)方法的语法糖
(4)作用: 用来简化创建虚拟 DOM
写法: var ele = <h1>Hello JSX!</h1>
注意 1:它不是字符串,也不是 HTML/XML 标签
注意 2:它最终产生的就是一个JS 对象
(5) 标签名任意: HTML 标签或其它标签
JSX语法规则:
1.定义虚拟DOM时,不要写引号
2.标签中混入JS
表达式
时要用{ }
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={
{key:value}}的形式去写。
5.只有一个根标签
6.
标签必须闭合
7.标签首字母
(1).若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
一定注意区分: [js语句(代码)] 与[js表达式]
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
以下都是
表达式
:
(1).a
(2).a+b
(3).demo(1)
(4). arr.map( )
(5). function test ( ) { }
2.语句(代码):
下面这些都是语句(代码):
(1).if( ){ }
(2).for( ){ }
(3).switch( ){case:xxxx}
动态展示列表
<script type="text/babel">
//模拟一些数据
const data = ['Angular', 'React','Vue']
//1.创建虚拟
DOMconst 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>
模块与组件、模块化与组件化的理解
模块
(1)理解:向外提供特定功能的 js 程序,一般就是一个js 文件
(2)为什么要拆成模块: 随着业务逻辑增加,代码越来越多且复杂。
(3)作用: 复用js, 简化js 的编写, 提高 js 运行效率
组件
(1)理解: 用来实现局部功能效果的代码和资源的集合(html/css/js/image 等等)
(2) 为什么: 一个界面的功能更复杂
(3) 作用: 复用编码, 简化项目编码, 提高运行效率
模块化
当应用的 js 都以模块来编写的, 这个应用就是一个模块化的应用。
组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用。
React 面向组件编程
基本理解和使用
使用 React 开发者工具调试
<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>
类相关知识
//创建一个Person类
class Person {
//构造器方法
constructor(name,age){
//构造器中的this是谁? --类的实例对象
this.name = name
this.age = age
}
//一般方法
speak(){
//speak方法放在了哪里? --类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是$this.age}`
//模板字面量
};
}
//创建一个Person的实例对象
const p1 = new Person('tom',18)
const p2 = new Person('jerry',19)
console.log(p1);
console.log(p2);
p1.speak()
p2.speak()
//.call()方法的作用就是在指定的this对象上调用函数,并将函数的参数和this作为参数传递给该函数。
//通常情况下,可以通过.call()方法来改变函数内部的this所指向的对象。
//创建一个student类,继承于Person类
class Student extends Person {
constructor(name,age,grade){
super(name, age)
this.grade = grade
}
//重写从父类继承过来的方法
speak(){
console.log(我叫${this,name},我年龄是${this,age],我读的是${this,grade}年级);
}
study(){
//study方法放在了哪里? -类的原型对象上,供实例使用
//通过student实例调用study时,study中的this就是student实例
console.log('我很努力的学习');
}
}
⭕总结:
1.类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
3.类中所定义的方法,都是放在了类的原型对象上,供实例去使用。
//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,随后呈现在页面中。
组件实例三大核心属性-state
class weather extends React.Component
{
//构造器调用几次?-- 1次
//constructor(props){
// super(props)
// 🌟 this.state = {isHot:false}
//借助构造器,初始化状态
//🌟解决changeWeather中this指向问题
//this.changeweather = this.changeweather.bind(this)
// }
state = {isHot;false,wind; '微风'} //初始化状态
//render调用几次? 1+n次 1是初始化的那次 n是状态更新的次数
render(){
//读取状态
const {isHot} = this.state
//render中的this就是组件实例对象
//解构赋值
return <h1 onClick={this.changeWeather}>今天天气很{isHot ?'炎热':'凉爽'}</h1>
//作为事件回调使用
}
//changeWeather调用几次?点几次调几次
//⭕自定义方法——要用赋值语句的形式 + 箭头函数
//在箭头函数中使用 this 时,它会被绑定到定义该函数时所处的上下文中。
changeweather=() =>{
//changeWeather放在哪里?—— weather的原型对象上,供实例使用
//由于changeweather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined,而不是Window
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意: 状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
//严重注意: 状态(state)不可直接更改,下面这行就是直接更改!!!
// 🌟this.state.isHot = !isHot //这是错误的写法
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
function demo( ){
console.log("标题被点击了);
}
}
<button id="btn1”>按钮1</button>
<button id="btn2”>按钮2</button>
<butto onclick="demo()">按钮3</button>
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click',()=>{alert('按钮1被点击了')})
const btn2 = document .getElementById('btn2')
btn2.onclick = ()=>{ alert('按钮2被点击了') }
function demo(){
alert("按镇3被点击了')
}
函数调用是通过函数名和一组参数来调用一个函数,即把函数的控制权从调用方转移到被调用的函数之中。在函数调用时,调用方会将参数压入栈中,并跳转到函数的入口地址,被调用的函数接收到这些参数后进行计算,并返回相应的结果到调用方。
而直接调用则是直接跳转到另一段代码的执行位置,不经过函数入口。它通常不包含参数传递,所有需要的变量都需要自行定义或者使用全局变量。
<script type="text/javascript">
function demo( ){
console.log(this);
}
demo()
const x= demo.bind({a:1,b;2})
//将this即window改为一个对象
//返回一个新的函数,所以需要先赋值然后调用它
x( ) //直接调用
class Car{
constructor(name,price)
this .name = name
this.price = price
// this.wheel = 4
}
//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1
a=1
</script>
理解:
(1)state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
(2)组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新染组件)
强烈注意
(1) 组件中render 方法中的 this 为组件实例对象
(2) 组件自定义的方法中 this 为 undefined,如何解决?
- 强制绑定 this: 通过函数对象的 bind( )
- 箭头函数
(3) 状态数据,不能直接修改或更新——>setState
props
案例需求: 自定义用来显示一个人员信息的组件
(1).姓名
必须指定
,且为宇符串类型:
(2).性别为字符串类型,如果性别没有指定,默认为男
(3).年龄必须指定,且为数字类型
//创建类式组件
class Person extends React.Component{
static propTypes = {
//对标签属性进行类型、必要性的限制
name:PropTypes.string.isRequired,//限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,
speak:PropTypes.func,//限制speak为函数,因为function是定义函数关键字
}
//给类自身加属性
🌟static defaultProps = (
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
constructor(props){
//构造器是否接收props,是否传递给super,取决于: 是否希望在构造器中通过this访问props
//console.log(props);
super(props)
console.log('constructor',this.props);
}
render(){
//console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name ='jack'
//此行代码会报错,因为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,
//speak:PropTypes.func,//限制speak为函数,因为function是定义函数关键字
//}
//指定默认标签属性值
//Person.defaultProps = (
//sex:'男',//sex默认值为男
//age:18 //age默认值为18
//}
//渲染组件到页面
//批量传递标签属性
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document,getElementById('test1'))
ReactDOM.render(<Person name="tom" age=🌟"18" sex="女"/>,document,getElementByid('test2'))
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person{...p}/>,document.getElementById( 'test3'))
//React的{}花括号作为分隔符使用,因此展开运算符能够使用
function speak(){
console.log('我说话了');
}
多级结构需要括号
扩展运算符是三个点
…
,它可以将一个可迭代对象(如数组、字符串等)展开为其包含的各个元素。这样做可以使我们更方便地组合、拷贝和切分数组,以及访问对象的属性。
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);
静态 static 属性适用于所有实例,属性被绑定在类本身上,而不是类的实例上。因此,它们可以被所有的实例共享,并且可以通过类名直接访问,而实例属性仅适用于特定实例,不能被其他实例或类访问,并且只能通过实例变量名进行访问。
//创建函数式组件
function Person (props){
const {name,age, sex} = props
//解构赋值:可以快速、简洁地从数组或对象中提取值,并将这些值赋给变量。
//解构赋值支持在数组和对象中进行深层嵌套的操作,使得取出需要的值变得更加方便和灵活
return (
<ul>
<li>姓名: {name</li>
<li>性别: {sex}</li>
<li>年龄: {ageJ</li>
</ul>
)
}
Person.propTypes = {
name;PropTypes.string.isRequired,//限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//JavaScript 对象都有一个 __proto__ 属性,它指向该对象的原型对象,注意区分开来
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" sex="女” age=(18)/>,document.getElementById('test1'))
理解
(1) 每个组件对象都会有 props(properties 的简写)属性
(2) 组件标签的所有属性都保存在 props 中
作用
(1)通过标签属性从组件外向组件内传递变化的数据
(2)注意: 组件内部不要修改 props 数据
编码操作
(1)内部读取某个属性值
this.props.name
(2) 对 props 中的属性值进行类型限制和必要性限制
第一种方式(React v15.5开始已弃用):
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.mumber
}
第二种方式(新):
使用 prop-types 库进限制(需要引入 prop-types 库)
Person.propTypes = {
name: PropTypes .string.isRequired,
age: PropTypes.number
}
(3) 扩展属性:将对象的所有属性通过 props 传递
<Person {...person}/>
(4)默认属性值
Person.defaultProps = {
age: 18,
sex:'男'
}
(5) 组件类的构造函数
constructor(props){
super(props)
console.log(props)//打印所有属性
}
✍🏻案例效果
需求: 自定义组件功能说明如下
1.点击按钮,提示第一个输入框中的值
2.当第2个输入框失去焦点时,提示这个输入框中的值
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
render(){
return(
<div>
//字符串形式的ref——已过时并可能会在未来的版本被移除
<input ref="input1" type="text" placeholder="点击按饥提示数据"/>
<input ref={(currentNode)=>{this.input1 = currentNode}} placeholder="点击按饥提示数据"/>
<input ref={c =>this.input1 = c } type="text"placeholder="点击按银提示数据"/>
<buttonr onClick={this,showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
<input onBlur= {this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
理解
组件内的标签可以定义ref 属性来标识自己
回调ref中回调执行次数的问题
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成tlass 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的
React.createRef
调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)onClick or onclick事件,而不是使用的原生DOM事件——为了更好的兼容性
b.React中的事件是通过
事件委托方式
处理的(委托给组件最外层的元素)——为了委托
(2).通过event.target得到发生事件的DOM元素对象——不要过渡使用ref。
✍🏻收集表单数据
表单的组件分类
- 受控组件
- 非受控组件
//对象的使用
let a = 'name'
let obj = { } // {name: 'tom'}
obj[a] ='tom'
console.log(obj);
高阶函数:
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有: Promise、setTimeout、arr.map()等等
函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a){
return(b)=>{
return(c)=>{
return a+b+c
}
}
}
组件的生命周期
案例需求: 定义组件实现以下功能:
1.让指定的文本做显示/隐藏的渐变动画
2.从完全可见,到彻底消失,耗时2S
3.点击“不活了”按钮从界面中卸载组件
class Life extends React.Component{
death = ()=>{
//卸载组件
ReactDOM,unmountComponentAtNode(document.getElementById('test'))
}
//渲染组件
ReactDOM,render(<Life/>,document.getElementById('test'))
}
生命周期流程图(旧)
class Count extends React.Component{
constructor(props){
console.log('Count---constructor');
super(props)
this.state = {count:0}
}
//初始化状态
state = {count:0}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this,state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDoM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩了
componentwillMount(){
console.log('Count---componentwillMount');
}
//组件挂载完毕的钩了
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩了
componentwillUnmount(){
console.log('Count---componentwillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩了
componentwillUpdate(){
console.log('Count---componentwillUpdate');
}
//组件更新完毕的钩了
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
const {count} = this.state
return(
<div>
<h2>当前求和为: {count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>截组件</button>
<button onClick={this.forceJ>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//父子关系组件
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this,state.carName}/>
</div>
)
}
}
//子组件B
class B extends React.Component{
//第二次接收到props才会触发
//组件将要接收新的props的钩子
componentwillReceivePropsh{
console.log('B---componentwillReceiveProps');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩了
componentwillUpdate(){
console.log('B---componentwillUpdate');
}
//组件更新完毕的钩了
componentDidUpdate(){
console.log('B---componentDidUpdate');
render(){
return(
<div>B组件,接收到的车是:{this.props.carName}</div>
)
}
}
//渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
①初始化阶段:
由ReactDOMrender( )触发—初次染
- constructor( )
2. componentWillMount( )
3. render( )
- componentDidMount( )—— 一般在这个钩子中做一些初始化的事,例如:开始定时器,发送网络请求,订阅消息
②
更新阶段:
由组件内部this.setSate( )或父组件重新render触发
1. shouldComponentUpdate ( )
2. componentWillUpdate( )
3. render( )
4. componentDidUpdate( )
③卸载组件:
由 ReactDOMunmountComponentAtNode( )触发
1. componentWillUnmount( )—— 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
废弃了componentWillReceiveProps、componentWillount、componentWillupdate而新增加了getDerivedStateFromProps、getSnapshotBeforeUodate
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate' ,preProps,preState,snapshotValue);
}
1.初始化阶段: 由ReactDom.render()触发—初次渲染
1.constructor( )
2.getDerivedStateFromProps
3 .render( )
4.componentDidMount( )
2.更新阶段: 由组件内部this.setSate( )或父组件重新render触发
1.getDerivedStateFromProps
2.shouldComponentUpdate()
3.render()
4.getSnapshotBeforeUpdate
5.componentDidUpdate( )
3.卸载组件:由ReactDoM.unmountComponentAtNode( )触发
componentwillUnmount()
重要的勾子
- render: 初始化渲染或更新染调用
- componentDidMount: 开启监听,发送ajax请求
- componentWillUnmount: 做一些收尾工作,如: 清理定时器
即将废弃的勾子
1.componentWillMounte
2.componentWillReceiveProps
3.componentWillUpdatee
现在使用会出现警告,下一个大版本需要机上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
🌟经典面试题:
(1)react/vue中的key有什么作用? key的内部原理是什么?
(2)为什么遍历列表时,key最好不要用index?
①虚拟DOM中key的作用:
(1)简单的说: key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
(2)详细的说: 当状态中的数据发生变化时,随后React进行[新虚拟DOM]react会根据[新数据]生成[新的虚拟DOM]与[旧虚拟DOM] 的diff比较,比较规则如下:
a.日虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变,直接使用之前的真实DOM
(2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key根据数据创建新的真实DOM,随后渲染到到页面
②用index作key可能会引发的问题:
1.若对数据进行:序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。
2.如果结构中还包含
输入类的DOM
: 会产生错误DOM更新 ==> 界面有问题
3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
③开发中如何选择key?
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
react 应用(基于react 脚手架)
使用 create-react-app 创建 react 应用
react 脚手架
xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目
包含了所有需要的配置(语法检查、jsx编译、devServer…)
下载好了所有相关的依赖
可以直接运行一个简单效果
react提供了一个用于创建react 项目的脚手架库: create-react-app
项目的整体技术架构为: react + webpack + es6 +eslinte
使用脚手架开发的项目的特点: 模块化、组件化、工程化。
建项启动
第一步,全局安装:npm i-g create-react-appu
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-reacte
第四步,启动项目:npm start
<head>
<!-- %PUBLIC URL%代表public文件来的路径 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- 开启理想视口,用于做移动端网页的适配 -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 用于指定网页添加到手机主屏幕后的图标 -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- 应用加壳时的配置文件 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
</head>
<body>
<!-- 若游览器不支持js则展示标签中的内容 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
public—-静态资源文件夹
favicon.icon —— 网站页签图标
index.html ——–主页面
logo192.png——-logo图
logo512.png——-logo图
manifestjson—– 应用加壳的配置文件
robots.txt——–爬虫协议文件
src —- 源码文件夹
App.css——– App组件的样式
App.js ———App 组件
App.test.js —- 用于给App 做测试
index.css——样式
index.js ——- 入口文件
logo.svg ——-logo图
reportWebVitals.js
—页面性能分析文件(需要web-vitals 库的支持)
setupTests.js
——组件单元测试的文件(需要jest-dom 库的支持
//index.js
//引入react核心库
import React from 'react'
//引入ReactDOM
importReactDOM from 'react-dom'
//引入App组件
import App from./App
//渲染App到页面
ReactDoM.render(<App/>,document.getElementById('root'))
//App.js
//解构赋值允许我们从数组或对象中提取数据并将其赋值给变量,还支持嵌套对象和数组的操作
//分别暴露是指在一个模块文件中,将多个变量或函数分别导出,以供其他模块使用。
//创建“外壳”组件App
import React,{Component} from 'react'
//创建并暴露App组件
export default class App extends Component{
render( ){
return(
<div>
hello,react!
</div>
)
}
}
功能界面的组件化编码流程
1.拆分组件 拆分界面抽取组件
2.实现静态组件: 使用组件实现静态页面效果
3.实现动态组件
(1)动态显示初始化数据
a.数据类型
b.数据名称
c.保存在哪个组件?
(2)交互(从绑定事件监听开始)
当应用是以多组件的方式实现,这个应用就是一个组件化的应用。
组件的组合使用-TodoList
keycode已经废除了,用event.key ! = ‘Enter’
状态在哪里,操作状态的方法就在哪里
闭包(closure)
是指在一个函数内部定义的函数,并且该内部函数可以访问外部函数作用域中的变量,即使在外部函数被调用完毕后,这些变量仍然可以被内部函数使用。
特点主要有两个:
- 闭包可以捕获外部函数作用域中的变量。
- 闭包可以访问并修改闭包外部变量的值。
todo放到App.jsx里面,并且子传子,父传子
//对接收的props进行: 类型、必要性的限制——它是一个函数类型,并且它是必需的
static propTypes = {
addTodo: PropTypes.func.isRequired
}
JavaScript中的
event
指的是事件对象(Event Object),是在特定操作发生时由浏览器自动创建的一个对象,包含了与该事件相关的各种信息和属性。常见的事件包括点击事件、鼠标移动事件、键盘按下事件等等。通过添加事件监听器(Event Listener),可以在特定事件发生时执行相应的 JavaScript 代码。
- 展开表达式:展开表达式可以将一个可迭代对象(如数组、字符串或类数组对象)展开为单独的元素,这些元素可以在一个表达式中使用,例如作为参数传递给一个函数或在数组或对象字面量中添加到另一个数组或对象中。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 将 arr2 中的元素展开到 arr1 中
const newArr = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
- 解构赋值:解构赋值可以从一个数组或对象中提取值并将其分配给变量。解构赋值允许您将属性名或数组索引与变量匹配,以便有效地访问和使用它们。
const person = {
firstName: "John",
lastName: "Doe",
age: 25,
};
// 使用解构赋值从 person 对象中获取属性
const { firstName, lastName, age } = person;
console.log(firstName); // "John"
console.log(lastName); // "Doe"
console.log(age); // 25
confirm( )
是 JavaScript 提供的一种用于在浏览器中创建确认对话框的方法。它通常用于提示用户是否继续进行某项操作。
当 confirm( ) 被调用时,将弹出一个包含指定消息和两个按钮(确定和取消)的对话框。如果用户点击“确定”按钮,则 confirm() 返回 true,否则返回 false。因此,可以通过检查 confirm() 的返回值来确定用户是否已确认某个操作。
reduce( )
是JavaScript数组的一个方法,用于对数组元素进行累加计算。
该方法接受一个回调函数作为参数,回调函数需要两个参数:累加器(accumulator)和当前值(currentValue),以及可选的起始值(initialValue)。回调函数执行后会返回一个新的累加器值。
reduce( )方法可以用于对数组中的所有元素进行求和、求积、拼接等操作。
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 10);
console.log(sum); // 输出25
ToDoList案例相关知识点
1.拆介组件、实现静态组件,注意: className、style的写法
2.动态初始化列表,如何确定将数据放在哪个组件的state中?
-某个组件使用: 放在自身的state中
-某些组件使用: 放在他们共同的父组件state中(官方称此操作为: 状态提升)
3.关于父子之间通信:
(1)[父组件]给[子组件] 传递数据: 通过props传递
(2)[子组件] 给[父组件] 传递数据: 通过props传递,要求父提前给子传递一个函数
4.注意defaultChecked 和 checked的区别,类似的还有: defaultValue 和 value
5.状态在哪里,操作状态的方法就在哪里
react Ajax
1.前置说明
①React本身只关注于界面并不包含发送ajax请求的代码
②前端应用需要通过ajax请求与后台进行交互(json 数据)
③react应用中需要集成第三方ajax库(或自己封装)
2.常用的ajax请求库
①jQuery: 比较重如果需要另外引入不建议使用
②axios: 轻量级建议使用
(1)封装XmlHttpRequest 对象的ajaxe
(2) promise风格
(3)可以用在浏览器端和node服务器端
同源策略(Same Origin Policy)
同源策略是一种安全机制,它定义了一个文档或脚本只能与来自同一源的资源进行交互,而不能与其他源的资源进行交互。其中,源指的是协议、主机和端口号的组合。
同源策略的目的是保护用户隐私和安全,防止恶意网站窃取用户数据或进行 XSS 攻击等不良行为。
跨域(Cross-Origin)
跨域指的是当一个文档或脚本试图访问另一个源的资源时,就会触发浏览器的同源策略,从而导致该访问被拒绝。跨域问题一般出现在以下场景中:
- Ajax 请求:由于 Ajax 是异步请求,所以会受到同源策略的限制。
- Cookie 读取:浏览器的 Cookie 是基于域名来管理的,因此不能读取其他域名下的 Cookie。
- iframe 嵌套:由于 iframe 中的内容也受同源策略影响,所以在跨域的情况下可能无法正常加载。
①在package.json中追加如下配置
proxy”:”http://localhost:5000″
说明:
1.优点: 配置简单,前端请求资源时可以不加任何前缀。
2.缺点: 不能配置多个代理。
3.工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
②
1.第一步: 创建代理配置文件
在src下创建配置文件: src/setupProxy.js
2.编写setupProxyis配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
target;'http://localhost:5000',//请求转发给谁
changeOrigin:true,//控制服务器收到的请求头中Host的值 服务器收到的请求头中的host为: ocahost:5000
//changeorigin设置为false时,服务器收到的请求头中的host为: 1ocalhost:3000
//changeorigin黑认值为false,但我们一般将changeOrigin值设为true
pathRewrite:{'^/api1':} //重写请求路径(必须)
})
)
}
说明:
1.优点: 可以配置多个代理,可以灵活的控制请求是否走代理
2.缺点: 配置繁琐,前端请求资源时必须加前缀。
fetch
await response.json( );
的作用是将从API获得的响应数据转换为JSON格式,并返回一个JavaScript对象或数组,以便在代码中进行操作和处理。
具体而言,await response.json( )将等待response对象返回一个JSON格式的响应数据,并将其解析成JavaScript对象。这个方法是异步执行的,所以在使用它的时候需要加上 await 关键字来等待结果返回。
特点:
1.fetch: 原生函数,不再使用XmlHttpRequest对象提交Ajax请求
2.老版本浏览器可能不支持
✍🏻github搜索案例相关知识点
1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
2.ES6小知识点: 解构赋值+重命名
let obj = {a:{b:111}}
const {a} = obj; //传统解构赋值
const {a:{b]} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
3.消息订阅与发布机制
1.先订阅,再发布 (理解: 有一种隔空对话的感觉)
2.适用于任意组件间通信
3.要在组件的componentwillUnmount中取消订阅
4.fetch发送请求(关注分离的设计思想)
try {
const response= await fetch( `/api1/search/users2?q=${keyword}`)
const data = await response.json()
console.log(data);
}catch (error) {
console.log('请求出错',error);
}
React 路由
SPA的理解
1.单页 Web 应用(single page web application,SPA)。
2.整个应用只有
一个完整的页面
3.点击页面中的链接
不会刷新页面
,只会做页面的
局部更新
4.数据都需要通过ajax请求获取并在前端异步展现。
路由的理解
1.什么是路由?
1.一个路由就是一个映射关系(key:value)
2. key为路径 value 可能是function或component
2.路由分类
①后端路由:
(1)理解: value是function, 用来处理客户端提交的请求。
(2)注册路由: router.get(path, function(req,res))
(3)工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求返回响应数据
②前端路由:
(1)浏览器端路由,value是component,用于展示页面内容。
(2)注册路由<Route path=”/test” component={Test}>
(3)工作过程:当浏览器的 path 变为 /test 时, 当前路由组件就会变为 Test 组件
“history.js” 是一个开源的 JavaScript 库,它允许使用 HTML5 History API(pushState,replaceState 和 onpopstate)与旧版浏览器兼容。它为开发人员提供了更好的控制历史记录,并使 AJAX 网站的导航和交互更加平滑
在网页内部实现锚点跳转需要使用HTML的锚点标签和超链接标签结合起来。具体步骤如下:
在要跳转到的位置添加锚点。在该位置的HTML代码中添加一个id属性,值为唯一的字符串,例如:
<h2 id="section1">Section 1</h2>
在要进行跳转的位置添加超链接标签。在该位置的HTML代码中使用<a>标签创建一个超链接,其中href属性的值为#加上要跳转到的锚点的id值,例如:
<a href="#section1">Go to Section 1</a>
前端路由原理
// let history = History.createBrowserHistory()
//方法一,直接使用H5推出的history身上的API
let history = History.createHashHistory() //方法二,hash值 (错点)
react-router-dom 的理解
1.react的一个插件库。
2.专门用来实现一个SPA应用。
3.基于react的项目基本都会用到此库。
react-router-dom 相关API
①内置组件
1.<BrowserRouter>
2.<HashRouter>
3.<Route>
4. <Redirect>
5.<Link>
6.<NavLink>
7. <Switch>
路由的基本使用
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
<Link to=”/xxxxx”>Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path=’/xxxx’ component={Demo}/>
4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
路由组件与一般组件
1.写法不同:
一般组件: <Demo/>
路由组件: <Route path=”/demo” component={Demo}/>
2.存放位置不同:
一般组件: components
路由组件: pages
3.接收到的props不同:
一般组件: 写组件标签时传递了什么,就能收到什么
路由组件: 接收到三个固定的属性
history:
go: f go(n)
goBack: f goBack( )
goForward: f goForward( )
push: f push(path, state)
replace: f replace(path, state)
location:
pathname: “/about”
search: “”
state: undefined
match:
params: { }
path: “/about”
url:”/about”
NavLink与封装NavLink
1.NavLink可以实现路由链接的高亮,通过activeclassName指定样式名
2.标签体内容是一个特殊的标签属性
3.通过this.props.children可以获取标签体内容
Switch的使用
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写
./
写
/
常用)
2.public/index.html 中 引入样式时不写
./
写
%PUBLIC URL%
(常用)
3.使用HashRouter
路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记: [输入的路径] 必须包含要[匹配的路径],[顺序要一致)
2.开启严格匹配:
<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用
1.一般写在所有路由注册的最下方
2.具体编码:
当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path="/about"component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
state = {
messageArr:[
{id:'01',title:'消息1'}
{id:'02',title:'消息2'}
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return(
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key=(msgobj.id}>
{/* 向路由组件传递params参数 */
<Link to=( /home/message/detail/$(msgObj.id}}>msgobj.title]</Link>
</li>
)
})
}
<hr/>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
</div>
)
向路由组件传递参数
①params参数
路由链接(携带参数):
<Link to=’/demo/test/tom/18 }>详情</Link>
注册路由(声明接收):
<Route path=”/demo/test/:name/:age” component={Test}/>
接收参数:
const {name,age} = this.props.match.params
(地址栏上有所体现)
②search参数
路由链接(携带参数):
<Link to=’/demo/test?name=tom&age=18′}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path=”/demo/test” component={Test}/>
按收参数:
this.props.location.search
备注: 获取到的search是urlencoded编码字符串,需要借助
querystring
解析
③state参数
路由链接(携带参数):
<Link to=[[path:’/demo/test’,state:{name:’tom’,age:18]}}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path=”/demo/test”component={Test}/>
接受参数:
this.rops.location.state
备注: 刷新也可以保留住参数
编程式路由导航
借助 this.prosp.history 对象上的 API 对操作路由跳转、前进、后退
-this.prosp.history.push( )
-this.prosp.history.replace( )
-this.prosp.history.goBack( )
-this.prosp.history.goForward( )
-this.prosp.history.go( )
push和replace
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件。
BrowserRouter HashRouter的区别
1.底层原理不一样:
BrowserRouter
使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.url表现形式不一样
BrowserRouter的路径中没有#,例如: localhost:3999/demo/testHashRouter的路径包含#,例如: localhost:3009/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在historv对象中。
(2).HashRouter刷新后会导致路由state参数的丢失。
4.备注: HashRouter可以用于解决一些路径错误相关的问题。
React UI组件库
流行的开源ReactUI组件库
material-ui(国外)
redux原理
简介:
1.redux是一个专门用于做
状态管理
的JS库(不是react 插件库)。
2.它可以用在react,angular, vue 等项目中,但基本与 react 配合使用。
3.作用:集中式管理react 应用中多个组件
共享
的状态。
什么情况下需要使用redux
1.某个组件的状态,需要让其他组件可以随时拿到(共享)
2.一个组件需要改变另一个组件的状态(通信)
3.总体原则:能不用就不用,如果不用比较吃力才考虑使用。
redux原理
1.动作的对象
2.包含2个属性
- type: 标识属性值为字符串,唯一, 必要属性
- data:数据属性值类型任意, 可选属性
3. 例子: {type:’ADD STUDENT’, data: { name: ‘tom’ age:18 } }
2. reducere
- 用于初始化状态、加工状态
-
加工时,根据旧的 state和action,产生新的 state 的
纯函数
3.store
将state、action、reducer联系在一起的对象。
//store.js 该文件专门用于暴露一个store对象,整个应用只有一个store对象
//引入createstore,专门用于创建redux中最为核心的store对象
import {createStore] from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count reducer'
//暴露store
export default createStore(countReducer)
//count_reducer.js
//1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
//2.reducer函数会接到两个参数,分别为: 之前的状态(prestate),动作对象(action)
1.求和案例_redux精简版
(1).去除Count组件自身的状态
(2).src下建立:
-src
-redux
-store.js
-count reducer.js
(3).store.js:
(1).引入redux中的createstore函数,创建一个store
(2).createstore调用时要传入一个为其服务的reducer
(3).记得暴露store对象
(4).count reducer.js:
(1).reducer的本质是一个函数,接收: preState,action,返回加工后的状态
(2).reducer有两个作用: 初始化状态,加工状态
(3).reducer被第一次调用时,是store自动触发的,传递的preState是undefined,传递的action是:{type:’@@REDUX/INIT_a.2.b.4}
(5).在index.js中检测store中状态的改变,一旦发生改变重新渲染<App/>
备注: redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。