https://www.bilibili.com/video/BV1eE411W7WT?p=7
# 一、初始react
# 1】react原理
# ①虚拟dom
react把DOM
抽象成为一个JS对象,通过这个JS对象来描述相关的页面中的真实DOM
,通过这个JS对象来实时的更新真实DOM
。这里的JS对象也就是虚拟DOM
。
# ②diff算法
react通过Diff算法
来保证当页面的DOM
更新的时候,不第一时间去更新真实DOM
,而是去更新虚拟DOM
。(因为页面每操作一个真实dom的时间相对比较长的,这里先更新虚拟DOM
来,然后最后统一来更新真实DOM
,大大地提高效率。)
# 虚拟DOM确保只对界面上真正发生变化的部分进行事件的DOM操作。
# 逐层次的来进行节点的比较。(一旦发现和之前有不同,直接删除,不用再进行比较)
# 2】react历史轴
2013年开始
# 二、基本环境搭建
# 1】react开发环境的搭建
react是JavaScript第三方库文件,我们需要开发的话,只需要在页面中引用react相关的文件就可以完成。
必须要映入三个文件:
- react.js (核心文件)
- react-dom.js (开发web的文件)
- 渲染页面中的DOM
- 当前文件依赖于react核心文件
- react-native.js (开发原生app的文件)
- 和react-dom.js文件属于两个分支,如果需要开发原生app才引用
- babel.js (ES6转换成ES5、JSX语法转换成JavaScript)
# 2】npm安装
# ①react核心包
npm i react --save
# ②react-dom
npm i react-dom --save
# ③babel
npm i babel-standalone --save
# 3】创建一个最简单的react项目
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="./node_modules/babel-standalone/babel.min.js"></script>
</head>
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">//一定要用这个"text/babel"否则不能编译jsx语法
let mydom=<h1>你好世界</h1>;
ReactDOM.render(mydom,document.getElementById("app"))
</script>
</body>
</html>
# 4】使用 create-react-app 快速构建 React 开发环境
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 Webpack + ES6 。
执行以下命令创建项目:(脚手架)
npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start
# 三、JSX语法基本使用
# 1】jsx中注释
相当于使用插值表达式
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
// 1.注释
let exegetic =
<h1>
你好世界{/*这里是需要注释的内容*/}
</h1>;
ReactDOM.render(exegetic, document.getElementById("app"));
</script>
</body>
# 2】jsx中使用多行标签
需要用一个父元素包裹(否则报错):
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
// 2.多行标签 需要用一个父元素包裹
let mydom =
<div>
<h1>文本一</h1><h1>文本二</h1>
</div>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
最终渲染出来的页面是:
文本一
文本二
# 3】jsx中使用表达式
如果想在jsx中使用表达式,那么我们就需要把表达式放入一对{ }
中。(插值表达式)
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
let text = "你好啊";
let num = 2333;
function fun(obj) {
return `姓名是${obj.name}-----年龄是${obj.age}`
}
let user = {
name: '小明',
age: 12
}
let mydom =
<div>
<p>{text}</p>
<p>{num}</p>
<p>{num+1}</p>
<p>{fun(user)}</p>
</div>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
最终渲染出来的页面是:
你好啊
2333
2334
姓名是小明-----年龄是12
# 4】使用三元/三目运算符
三元运算符格式:判断表达式?真即执行:假即执行
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
let phone = 4999;
let mydom =
<div>
当前手机价格是{phone}----{phone>4000?"很贵":"很便宜"}
</div>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
最终渲染出来的页面是:
当前手机价格是4999----很贵
# 5】渲染数组(列表渲染)
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
let Arr = [
<p>新闻列表1</p>,
<p>新闻列表2</p>,
<p>新闻列表3</p>,
<p>新闻列表4</p>,
<p>新闻列表5</p>,
<p>新闻列表6</p>
]
let mydom =
<div>
{Arr}
</div>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
最终渲染出来的页面是:
新闻列表1
新闻列表2
新闻列表3
新闻列表4
新闻列表5
新闻列表6
# 6】jsx中标签属性动态设置
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
let text = "去百度"
let linkUrl = "http://www.baidu.com"
let mydom =
<a href={linkUrl}>{text}</a>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
最终渲染出来的页面是:
# 7】jsx设置style内联样式
style属性可以一直使用对象来设值,不一定使用字符串。
let modStyle = {
color: 'red', backgroundColor: 'pink'
}
let mydom =
<p style={modStyle}>修改我的内联样式</p>;
//注意jsx中不能出现class这个属性,因为class是js的关键字
ReactDOM.render(mydom, document.getElementById("app"))
# 8】jsx设置类使用className属性
注意:不能使用class,因为class是js的关键字
<style>
.modClass{
color:blue;
}
</style>
<body>
<!-- 创建dom根节点 -->
<div id="app"></div>
<script type="text/babel">
let mydom =
<p className="modClass">修改我的内联样式</p>;
ReactDOM.render(mydom, document.getElementById("app"))
</script>
</body>
# 四、react列表渲染
# react列表渲染的实质:
在render()
方法中直接传入模板数组来进行渲染,比如:
render([<p key="0">哈哈哈</p>,<p key="1">呵呵呵</p>,<p key="3">嘻嘻嘻</p>],document.getElementById("app"))
react会在render()
函数中根据key
值自动进行渲染。
# 1】使用map()来列表渲染
//react列表渲染 map()
let arr =['吃饭','睡觉','打豆豆'];
let mydom =arr.map((item,index)=>{
//key必须是独一无二的
return (//要换行的话,一定要在return后面加上小括号
<p key={index}>{item}</p>
)
})
ReactDOM.render(mydom, document.getElementById("app"))
# 2】使用for in循环
let arr = ['吃饭', '睡觉', '打豆豆'];
let fun=function () {
let newarr = [];
for (let index in arr) {
newarr.push(<p key={index}>{arr[index]}</p>)
}
return newarr
}
ReactDOM.render(fun(), document.getElementById("app"))
# 3】列表循环动态修改子元素的样式
let arr = ['吃饭', '睡觉', '打豆豆'];
let index = -1;
let fun = function () {
let mydom = arr.map((v, i) => {
return (<p style={{color:i==index?'red':''}} key={i} onClick={() => {
index = i;render();
}}>{v}</p>)
})
return mydom
}
function render(){
ReactDOM.render(fun(), document.getElementById("app"))
}
render()//初次跑一次程序渲染,才能出现内容,才可以有点击事件
# 五、遍历对象
# 1】js对象属性的访问
var obj={
name:"小明",
age:18
}
console.log(obj.name)//小明
console.log(obj["age"])//18
//通过点的方式会方便很多
//但是要注意:如果key是一个变量的话,就不能使用点这种方式来取值。
console.log(Object.keys(obj))
//返回一个数组类型的数据,得到对象中的所有键(key)
//["name","age"]
console.log(Object.values(obj))
//返回一个数组类型的数据,得到对象中所有属性的值(value)
//["小明",18]
# 2】react渲染列表对象
先把对象通过Object.keys()
方法以数组的形式返回该对象的所有键,然后再通过obj[key]
的方法访问对象中单个属性的值。
let obj = {
name: "小明", age: 18, sex: "男"
}
let mydom = <div>
{
Object.keys(obj).map((key, index) => {
return <p key={index}>{obj[key]}</p>
})
}
</div>
ReactDOM.render(mydom, document.getElementById("app"))
# 六、react面向组件编程基础
# 1】组件介绍
# 特点:高耦合低内聚
高耦合就是说逻辑紧密的内容放在一个组件中
低内聚就是说不同组件的依赖关系尽量弱化,每个组件能尽可能的独立起来
# 组件中的重要内容:
1.构建的方式
2.组件的属性
3.生命周期
# 演变过程:
传统的组件有几个明显的特点,1.简单的封装 2.简单的生命周期的呈现 3.明显的数据流动
当一个项目比较复杂的时候,传统的组件化根本不能很好的把结构样式和行为结合,让项目很难维护。
# react组件分为3个部分:
1.属性props
2.状态state
3.生命周期
通过组件可以把页面中的ui部分切分成独立、高复用性的部分,让每个开发者更加专注于一个个独立的部分。
# 组件与组件化:
组件就是用实现页面局部功能的代码集合,简化页面复杂程度,提高运行效率。
最简化就是当前程序都是使用组件完成的, 那么就是一个组件化的应用。
# 2】组件的创建
# 1.函数组件/无状态组件:
//无状态组件的创建方式:
function Mycom(){
return (
<div>我是一个无状态组件</div>
)
}
let com=<Mycom/>
ReactDOM.render(com,document.getElementById('app'));
//父子组件:
//子组件
function MycomA(){
return (
<div>我是第一组件</div>
)
}
function MycomB(){
return (
<div>我是第二组件</div>
)
}
function MycomC(){
return (
<div>我是第三组件</div>
)
}
//父组件调用
function Com(){
return (
<div>
<MycomA/>
<MycomB/>
<MycomC/>
</div>
)
}
ReactDOM.render(<Com/>,document.getElementById('app'));
# 2.类组件(组件名首字母大写)
//创建类组件 组件名首字母大写
class MyCom extends React.Component {
render(){
return (
<div>类组件</div>
)
}
}
let com=<MyCom/>
ReactDOM.render(com,document.getElementById('app'));
# react
类组件的构造方法 constructor():
constructor(props){
super(props);
this.state = {
}
}
constructor
必须用super()
初始化this
,可以绑定事件到this
如果你在
constructor
中要使用this.props
,就必须给super
加参数,super(props)
;constructor(){ super(); this.state = { xxx:this.props.xxx//构造函数里面用到了props }; }//这里如果在construct()和super()中不传入props参数,则会报错 //因为这里使用super()是重写了父类的构造函数(父类的构造函数本来是super(props,context))
无论有没有
constructor()
,重写的构造函数中参数有没有props
,render()
中都可以使用this.props
, 默认自带如果组件没有声明
constructor
,react
会默认添加一个空的constructor
。class News extends React.Component { render() { return ( <div> new组件:{this.props.text} <p>{this.state.text}</p> </div> ) } } //如果在继承时,子类中如果没有声明constructor,js会自动生成添加constructor并且调用父类的构造函数 //这里是父类的构造函数是super(props,context),并不是super()如果写super()则是不完全使用父类构造函数方法。
ES6
采用的是先创建父类的实例this
(故要先调用super( )
方法),完后再用子类的构造函数修改this
# constructor( )
-----super( )的基本含义
constructor( )——构造方法
这是ES6对类的默认方法,通过 new 命令生成对象实例时自动调用该方法。
并且,该方法是类中必须有的,如果没有显示定义,则会默认添加空的constructor( )方法。
super( ) ——继承
在class方法中,继承是使用 extends 关键字来实现的。
子类必须在 constructor( )调用 super( )方法,否则新建实例时会报错。
(如果子类没有显示定义构造方法,js则会默认加上构造方法并且里面用super自动调用父类的构造方法)
即:
class son extends father{
constructor(){
super()
}
}
如果父类构造方法里面含参数,子类没有显示定义构造方法,js也会默认向子类构造方法中加上参数调用父类的构造方法,例如:
class son extends father{
constructor(name,age){
super(name,age)
}
}
报错的原因是:子类是没有自己的 this 对象的,它只能继承自父类的 this 对象,
然后对其进行加工,而super( )就是将父类中的this对象继承给子类的。
没有 super,子类就得不到 this 对象。
# 下面我们来举个很好的栗子,说明这一切:
下面是子类显示定义了构造方法,但是并没有向子类构造方法传入父类构造方法本应该传入的形参:
class father {
constructor(name, age) {
this.name = name
this.age = age
}
pick() {
console.log(`年龄:${this.name}`, `年龄:${this.age}`)
}
}
class son extends father {
//显示定义了构造方法,但是没有向其中传入父类该有的形参
constructor(name){//这里我们只传入第一个参数
super(name)
}
pull() {
console.log("jhhh ")
}
}
Father = new father("baba", 40)
Father.pick()//年龄:baba 年龄:40
Son = new son("erzi", 20)
Son.pick()//年龄:erzi 年龄:undefined
//可以看到此时子类虽然使用super继承使用了父类的构造方法,但是只会使用了父类的一个形参
class father {
constructor(name, age) {
this.name = name
this.age = age
}
pick() {
console.log(`年龄:${this.name}`, `年龄:${this.age}`)
}
}
class son extends father {
//此时子类直接继承父类,并没有显示定义构造方法
pull() {
console.log("jhhh ")
}
}
Father = new father("baba", 40)
Father.pick()//年龄:baba 年龄:40
Son = new son("erzi", 20)
Son.pick()//年龄:erzi 年龄:20
//此时我们可以知道如果子类没有显示定义构造方法,
//则会默认补上父类的构造方法,而且构造方法的参数和父类都是一致的。
总结:通过这个我们知道如果在react类组件中写出构造函数而且需要在构造函数中使用props属性,必须补上第一个形参作为补位,才能使用得到props
的值。
而这里需要注意的是:react类组件在render()
函数中无论构造函数有没有传入props形参,render()
函数都可以使用到this.props
的值
# 注意:
类组件需要注意js内置事件回调函数的this指向问题。
1. 类组件有自己的状态
2. 继承React.Component-会有生命周期和this
3. 内部需要一个render函数(类组件会默认调用render方法,但不会默认添加,需要手动填写render函数,并return一个能渲染的值。)
4. 类组件的基本架构
5. constructor里边添加自己的属性和状态、方法
a. 写了constructor就要写super
b. 如果c里边没内容只有super,name可以不写
6. 添加状态this.state = {}; es7写法state = {}。非双向绑定
7. setState接收对象的情况、批量更新
8. setState接收函数的情况、state与penddingState
9. class里方法的写法
a. 方法里边this的指向undefined的情况
class Person {
fn(){
console.log(this);
}
}
var person = new Person();
var fn1 = person.fn;(这里是将值赋给fn1)
fn1(); //undefined(fn1()当前的指向位置this指向undefined)
b. bind改变this指向
c. 箭头函数继承this指向
d. 匿名函数传参
10. TodoList实战
11. 类组件注意:
注意绑定事件时,"on"后边事件名的首字母大写,如"change"要写成"Change"注意回调函数内部this的指向默认为undefined,要改变this指向
不能直接改变state的值、需要用函数setState来修改state的值
类组件内部没有render函数报错:
因为看到class组件会默认调用render方法 如果看到函数组件,会自动在函数内部添加一个render方法,把函数的return返回值放到render中运行。 所以类组件内部必须有render函数,并return返回一个可渲染的值。不会进行自动添加。
# React中自定义事件this指向问题:
this
是基于函数的执行环境(也就是上下文)绑定的,React组件生命周期函数中this
的上下文就是组件实例。(而js
的变量是通过词法作用域来绑定的)
- 可以看关于this的一篇文章总结的很到位。
- 1.函数在调用时,JavaScript会默认给this绑定一个值;
- 2.this的绑定和定义的位置(编写的位置)没有关系;
- 3.this的绑定和调用方式以及调用的位置有关系;
- 4.this是在运行时被绑定的;
你必须谨慎对待 JSX
回调函数中的this
,类的自定义方法默认是不会绑定 this
的。首先调用 constructor()
函数, this.state
的this
上下文就是该实例对象;同理,render()
函数中this
也是该实例。
在浏览器中回调函数中的this默认是指向window
的,因为本质上是在函数内callback
,并没有.前
的对象调用,在**nodejs
中的回调函数中的this默认是undefined
**。(回调函数可以参看我之前的总结)
class Bar extends React.Component {
constructor (){
super();
this.state = {
value: 'react'
}
}
changeValue (){
console.log(this) // undefined
}
render (){
console.log(this) // 该组件实例
return (
<div>
<p>{this.state.value}</p>
<button onClick={this.changeValue}>click me !</button>
</div>
)
}
}
当我们直接绑定this.changeValue
调用时,会发现他的this
是undefined
;你应该为和这个方法绑定this
。
解决方式:
①:使用 bind()
函数改变this
的上下文(这里只能用bind,不能用call和apply,因为call和apply会立即执行,而bind不会)
这里还是举个栗子来看一下区别:
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.bind(obj, ['Dot', 'Dolby'])//不会执行打印函数,而是返回一个改变了执行上下文this指向之后的函数
getName.bind(obj, ['Dot', 'Dolby'])()// My name is: Dot Dolby
getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby
getName.call(obj, 'Dot', 'Dolby')// My name is: Dot Dolby
在这里就可以使用bind来改变回调函数上下文this的指向,并不需要让它马上执行
class Bar extends React.Component {
constructor (){
super();
this.state = {
value:'react'
}
}
changeValue (e){
console.log(e) // 默认event
this.setState({
value:`react ${parseInt(Math.random()*100)}`
})
}
render (){
return (
<div>
<p>{this.state.value}</p>
<button onClick={this.changeValue.bind(this)}>click me !</button>
</div>
)
}
}
也可以在constructor()
中:this.changeValue = this.changeValue.bind(this)
②:es6
的箭头函数
利用箭头函数将函数的this
绑定到其定义时所在的上下文
<div>
<p>{this.state.value}</p>
<button onClick={(event) => this.changeValue(event)}>click me !</button>
</div>
或者
changeValue = (e) => {
console.log(e) // 默认event
this.setState({
value:`react ${parseInt(Math.random()*100)}`
})
}
# 3】属性props
1.props
是react
中一个重要的属性,是组件对外的接口,我们使用props
就可以从组件的外部向组件的内部进行数据的传递。
2.也可以完成父组件给子组件的数据传递。
3.注意:无论是无状态组件还是类组件,我们都不能修改自身的props
,因为react是单向的数据流。
# ①无状态组件使用props
//使用Props
function Com(props){
return (
<div>我是一个无状态组件-----外部传递的数据是:{props.text}</div>
)
}
ReactDOM.render(<Com text="我是传递给com的props的数据"/>, document.getElementById('app'));
页面上渲染出的效果:我是一个无状态组件-----外部传递的数据是:我是传递给com的props的数据。
在react的无状态组件中使用props属性,向外部父组件暴露传递值得接口。
# 用法:
1.在子组件中用参数传入props
。
2.在返回的jsx语法中使用插值表达式{props.属性名}
来使用。
3.在外部直接在组件标签内使用属性名="xxxx"
,来传递值。
4.如果要在标签内的属性设置动态的变量也是需要用插值表达式:属性名={定义的变量名}
function Com(props){
return (
<div>我是一个无状态组件-----外部传递的数据是:{props.text}</div>
)
}
let value="我是变量"
ReactDOM.render(<Com text={value} />, document.getElementById('app'));
页面中渲染出的效果:我是一个无状态组件-----外部传递的数据是:我是变量。
5.如果我们需要传递多个props属性进入组件中,可以**使用ES6中的扩展运算符{...obj}
**直接写入标签。用法如下:
正常的传入多个props属性用法:
function Com(props){
return (
<div>我是一个无状态组件-----外部传递的数据是:{props.text}---{props.num}</div>
)
}
let value="我是变量"
ReactDOM.render(<Com text={value} num="111"/>, document.getElementById('app'));
页面渲染出的效果:我是一个无状态组件-----外部传递的数据是:我是变量---111
使用ES6
扩展运算符{...obj}
传入标签的用法:
function Com(props) {
return (
<div>我是一个无状态组件-----外部传递的数据是:{props.text}---{props.num}</div>
)
}
let obj = {
text: "我是文本值",
num: "222"
}
ReactDOM.render(<Com {...obj} />, document.getElementById('app'));
页面渲染出的效果:我是一个无状态组件-----外部传递的数据是:我是文本值---222
# ②类组件使用props
//类组件使用props
class Com extends React.Component {
render() {
return (
<div>
我是类组件---{this.props.name}---{this.props.num}
</div>
)
}
}
ReactDOM.render(<Com name="name的数据" num="num的数据"/>, document.getElementById('app'));
//页面渲染出的效果:我是类组件---name的数据---num的数据
在组件继承的React.Component
类中就定义了props属性
直接使用this.props.属性名
来使用。
同理如果传入多个props属性值
也可以使用ES6的扩展运算符{...obj}
来像标签中传入多个属性值:
//使用es6扩展运算符传入props属性值:{...obj}(插值表达式大括号+扩展运算符)
class Com extends React.Component {
render() {
return (
<div>
我是类组件---{this.props.name}---{this.props.num}
</div>
)
}
}
let obj = {
name: 'name的数据',
num: 'num的数据'
}
ReactDOM.render(<Com {...obj} />, document.getElementById('app'));
//页面渲染出的效果:我是类组件---name的数据---num的数据
# ③无状态组件的props的验证与默认值
//无状态组件的props的验证与默认值
function Com(props) {
return (
<div> 我是无状态组件---{props.name}</div>
)
}
Com.defaultProps = { name: '我是name的默认值' }//初始化defaultProps属性中的name
ReactDOM.render(<Com />, document.getElementById('app'));
//我是无状态组件---我是name的默认值
//此时如果在<Com />标签内对name属性进行赋值,则会覆盖defaultProps中name的值
ReactDOM.render(<Com name="我是name的数据" />, document.getElementById('app'));
//我是无状态组件---我是name的数据
# props验证:验证传递进来的数据是否符合我们期望了类型或者要求,(上线模式中请取消props验证)
//无状态组件的props类型验证
//1.引用prop-types库 npm i -S prop-types
//注意需要引入的是prop-types.js文件,而不是上线版的prop-types.min.js文件
function Com(props) {
return (
<div> 我是无状态组件---{props.name}---{props.age}</div>
)
}
Com.propTypes = {
name: PropTypes.number,//验证name这个props传递进来的数据必须是number类型
age: PropTypes.number.isRequired
}
// let num = "333"
let num = 222
ReactDOM.render(<Com name={num} age={num} />, document.getElementById('app'));
//当num是字符串的时候,页面依然会渲染出来,但是控制台会报错
//当num是数字型的时候,就能完美运行。
# ④类组件的props默认值和props验证
// 类组件组件的props的验证与默认值
class Com extends React.Component {
render() {
return (
<div>我是类组件 ---{this.props.name}</div>
)
}
}
//props默认值设置
Com.defaultProps={
name:'name的默认值'
}
//propTypes的对props的验证(方法与无状态组件一致)
Com.propTypes = {
name: PropTypes.number,//验证name这个props传递进来的数据必须是number类型
}
ReactDOM.render(<Com />, document.getElementById('app'));
//我是类组件 ---name的默认值
在类组件中还可以在组件类中直接使用static defaultProps
来设置默认值:
class Com extends React.Component {
static defaultProps={
name:'name的默认值'
}
render() {
return (
<div>我是类组件 ---{this.props.name}</div>
)
}
}
ReactDOM.render(<Com />, document.getElementById('app'));
//我是类组件 ---name的默认值
# 4】状态state
# ①state
状态的介绍
state
和props
的区别:
1.state
是组件内部可变的变量。
2.props
对于当前组件来说,它是只读的,如果我们想修改props
中的数据,那么我们需要修改父组件中的状态变量
,然后传递给props
,达成父传子通信的目的。
props
:是组件对外的接口,组件内可以引用其他组件,组件之间的引用就形成了一个树状的结构,如果子组件需要使用父组件的数据,父组件就可以通过子组件中的props
来进行数据传递。(所以说,这个props
也就是组件通信中的父传子的作用)
state
:是组件对内的接口,组件除了父传子通信之外,他自身也需要管理的数据,这个对内管理数据的属性就是state
。(类似于Vue
中的data
属性,这里也有个概念,react
也有对应的响应式和数据绑定原理。)
react
中我们只关心的是数据,当数据改变的时候页面就会自动的发生改变。
状态等同于页面中的数据,状态/数据改变了---页面中对应的数据绑定内容,就会被react
自动的进行改变。(这里的数据就是模板中的数据,这里的状态是指**js
中的变量来修饰数据的状态**)
声明式渲染:一切数据改变操作不用我们关心,只需要我们声明好数据,react自动的对于数据进行相应的改变。
注意:如果要使用状态,那么不能使用无状态的函数组件。
# ②状态组件state
的使用
# ES6类的知识补充
在es6中不管子类写不写constructor,都会在new的实例上补上**constructor方法里面用super()**执行父类的构造函数。
(相当于默认子类构造函数就是父类的构造函数)
class Employee{//父类
constructor(name,position){//定义构造方法,名字固定
this.name=name
this.position=position
}
}
class Manager extends Employee{//子类
constructor(name,position){
//可不写子类构造函数,不写就是默认执行父类构造函数
super(name,position)//调用父类的构造函数
}
}
如果想要子类重写父类的构造函数只需要自己写一个constructor方法并且向里面传入需要的参数自定义属性即可。如下:
class Employee{//父类
constructor(name,position){//定义构造方法,名字固定
this.name=name
this.position=position
}
}
class Manager extends Employee{//子类
constructor(name,position,dept){//重写构造函数,传入其他多的参数
super(name,position)//调用父类的构造函数
this.dept=dept//子类自定义新增属性
}
}
现在我们在来使用状态state
:
// 状态组件:
class Com extends React.Component {
//在es6中不管子类写不写constructor,都会在new的实例上补上constructor方法里面用super()执行父类的构造函数
//(相当于默认子类构造函数就是父类的构造函数)
//如果想要子类重写父类的构造函数只需要自己写一个constructor方法并且向里面传入需要的参数即可
constructor(props) {
super(props)
this.state = {
name: '嘻嘻'
}
}
render() {
//this.setState({key:newValue}) 是异步的,调用react会自动地触发render()进行数据的渲染
return (<div>
<button onClick={() => {this.setState({name:'hhh'})}}>点我改变state的数据</button>
<div>我是的state的值{this.state.name}</div>
</div>
)
}
}
ReactDOM.render(<Com />, document.getElementById('app'));
this.setState({key:newValue})
是异步的,调用react会自动地**触发render
**进行数据的渲染。(这个异步还是得看情况,在react组件生命周期或react合成事件中是异步的;在setTimeout或原生dom事件中是同步的;)
# 注意:
1.可以在子类的构造函数中使用this.state={key:oldValue}
来使用,比如上面这种情况
2.也可以不把state
放在子类的构造方法中,也就是不用像上面一样重写子类的构造方法,直接在子类的原型中使用state
,如下:
class Com extends React.Component {
state = {
name: '嘻嘻'
}
render() {
return (<div>
<button onClick={() => {this.setState({name:'hhh'})}}>点我改变state的数据</button>
<div>我是的state的值{this.state.name}</div>
</div>
)
}
}
ReactDOM.render(<Com />, document.getElementById('app'));
# 5】ref转发(少用,不利于维护)
# ref
区别于state
:
ref
是对组件中的dom节点进行标记,在第一次执行render
函数进行渲染页面的时候就会将使用ref
属性的dom节点进行标记,然后可以通过ref
进行查询标记的dom节点,便于操作。(可以管理操作所有的dom节点和子组件)
如果ref
作用在组件上,对组件进行实例化,可以操纵组件,调用组件内部封装的一些函数。但是注意的是,函数组件不可以使用ref,因为函数组件没有实例。
state
是状态组件中管理自身组件状态的对象。(只负责自己)
# ref
的介绍:
react
当中提供了一个ref
属性(不能在无状态组件中来进行使用,因为无状态组件没有实例)
refs
表示当前组件的真正实例的引用,他返回绑定当前属性的元素。
作用:标识组件内部的元素---方便我们查找。
# 注意:
- 官方建议我们少使用
ref
对逻辑进行处理,需要优先考虑state
,使用ref
过多会到时候组件耦合度高,不利于维护。
# ref
的使用:
react
给我们三种方式进行ref
的使用:
- 字符串的方式
- 回调函数(推荐)
React.createRef()
(react16.3
新提供的一种方式)
# ①标签上的ref
属性赋值字符串的形式:(这里也可以通过dom查询来获取值)
class Com extends React.Component {
fun() {
console.log(document.querySelectorAll('input')[0].value);//打印输入的值
console.log(this.refs.demoInput.value);//打印输入的值
}
render() {
return (
<div>
我是类组件
<input type="text" ref="demoInput" placeholder="请输入" />
<button onClick={this.fun.bind(this)}>点我得到输入框的值</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
# ②回调函数(推荐)
就是在dom节点上或者组件上挂载函数,函数的形参是当前dom节点,它实现的效果和字符串的方式是一样的,都是获取当前dom元素
的引用。
class Com extends React.Component {
fun() {
console.log(this.dom.value)
}
render() {
return (
<div>
我是类组件
<input type="text" ref={(dom)=>{this.dom=dom}} placeholder="请输入" />
<button onClick={this.fun.bind(this)}>点我得到输入框的值</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
这里的ref={(dom)=>{this.varDom=dom}}
是指把当前这个标识的dom元素
用一个varDom这个变量属性来暂时保存,再来间接使用这个标识的dom元素
。
(这个ref
的回调函数是在第一次render()
渲染的时候触发执行)
# ③使用React.createRef()
然后用里面的current来取值
这里和上面的回调函数的方式差不多,也是把创建的ref标识值赋给一个变量,通过ref属性挂载在到当前dom元素上,使用React.createRef()
的current
属性来拿到这个节点。
class Com extends React.Component {
fun() {
console.log(this.Ref.current.value)
}
Ref=React.createRef()
render() {
return (
<div>
我是类组件
<input type="text" ref={this.Ref} placeholder="请输入" />
<button onClick={this.fun.bind(this)}>点我得到输入框的值</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
当第一次执行render函数渲染页面的的时候就会执行React.createRef()
,然后把返回值给当前储存标识ref
的变量Ref
,最后就可以使用Ref
(执行了React.createRef()
的返回值临时赋给了Ref
)里面的current
属性取出标识的当前dom元素。
# 七、react中的事件与this
# react事件处理:
react
绑定事件使用的是小驼峰命名法,在绑定回调函数的时候不能加小括号----->因为加了()
会导致回调函数立即执行。(这里绑定事件是需要回调函数触发执行)
# 修改this指向:
- 1.
bind
方法原地修改this指向- 只能使用
bind
不能使用call
和apply
(因为只有bind
方法才不会被立即执行,返回修改this
指向之后的函数)
- 只能使用
- 2.函数使用箭头函数
- 3.在
constructor()
中提前使用bind
修改this
指向 - 4.把事件调用时的整体函数改成箭头函数的方式然后箭头函数里面写立即执行函数
class Com extends React.Component {
constructor(props) {
super(props)
this.func = this.func.bind(this)
}
funa() {
console.log(this);
}
funb = () => {
console.log(this);
}
func() {
console.log(this);
}
fund() {
console.log(this);
}
render() {
return (
<div>
<button onClick={this.funa.bind(this)}>bind原地修改this</button>
<button onClick={this.funb}>箭头函数修改</button>
<button onClick={this.func}>在构造函数提前bind修改</button>
<button onClick={() => this.fund()/*箭头函数里面的函数需要立即执行*/}>事件调用方式使用箭头函数</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
//以上方式点击按钮都可以打印出this指向Com
# 事件里面回调函数进行实参传递:
class Com extends React.Component {
fune = (parameter) => {
console.log(parameter);
}
render() {
return (
<div>
<button onClick={this.fune("xxx")/*这种方式肯定不行,因为函数会立即执行*/}>点我进行实参传递</button>
<button onClick={this.fune.bind(this,"hhh")/*这里需要使用bind传参,返回的的函数不会立即执行*/}>点我进行实参传递</button>
<button onClick={()=>this.fune("jjj")/*这是使用函数嵌套的形式让外面的函数不立即执行,里面的函数可传参立即执行*/}>点我进行实参传递</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
# 传递事件event对象参数:
这个event对象是点击事件,执行回调函数时浏览器进行传入的event对象实参,我们需要在写回调函数时接受这个参数。
class Com extends React.Component {
funf = (parameter1,parameter2) => {
console.log(parameter1);
console.log(parameter2);
}
fung = (p) => {
console.log(p);
}
render() {
return (
<div>
<button onClick={(event) => this.funf("jjj",event)/*函数嵌套,设置一个形参,让发送点击事件的时候触发回调传入event实参对象*/}>点我进行event对象传递</button>
<button onClick={this.funf.bind(this,"xxx")/*会传递一个实参到funf中,funf还需要一个参数没有传,也就是点击事件时触发执行回调会传入的event*/}>点我进行event对象传递</button>
<button onClick={this.fung.bind(this)/*也可以在函数fung中设置一个形参,但是自己主动不设置实参,让发送点击事件的时候触发回调传入event实参对象*/}>点我进行event对象传递传递</button>
</div>)
}
}
ReactDOM.render(<Com />, document.getElementById('app'))
//点击前两个按钮都会打印出event对象
//点击最后一个按钮也可以直接打印出event对象
# 八、条件渲染
条件渲染是什么?
根据状态的变化只渲染其中的一部分。
# 1.if语句(jsx
模板中不允许有if
)
class Com extends React.Component{
state={
bool:true
}
fun=()=>{
this.setState({
bool:!this.state.bool
})
}
render(){
if(this.state.bool){
text='你好'
}else{
text='你坏'
}
return(
<div>
<button onClick={this.fun}>点我修改下面内容</button>
{text}
</div>
)
}
}
# 2.三元运算符(在jsx模板中可以使用)
class Com extends React.Component{
state={
bool:true
}
fun=()=>{
this.setState({
bool:!this.state.bool
})
}
render(){
return(
<div>
<button onClick={this.fun}>点我修改下面内容</button>
{this.state.bool?'哈哈哈':'呵呵呵'}
</div>
)
}
}
# 九、react状态提升
状态提升:多个组件需要反映相同的数据变化。把这些数据提升到离他们最近的一个父组件中,这个父组件改变了这些数据之后,再通过props
分发给子组件。
使用场景:多个子组件需要利用到对方状态的情况下,那么这个时候就需要使用到状态提升。
class ChildCom1 extends React.Component {render() {
return (
<div>
我是第一个组件{this.props.text}
</div>)
}
}
class ChildCom2 extends React.Component {
render() {
return (
<div>
我是第二个组件{this.props.text}
</div>)
}
}
class FatherCom extends React.Component {
state = {
context: '我是共用的数据'
}
fun() {
this.setState({
context: '改变了的数据'
})
}
render() {
//把两个子组件都需要用到的状态放在这个父组件中
return (
<div>
我是第父组件组件
<ChildCom1 text={this.state.context} />
<ChildCom2 text={this.state.context} />
<button onClick={this.fun.bind(this)}>点我改变子组件类容</button>
</div>)
}
}
ReactDOM.render(<FatherCom />, document.getElementById('app'))
# 十、react组件生命周期
组件的生命周期可分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
生命周期的方法有:
- componentWillMount 在渲染前调用,在客户端也在服务端。
- componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
- componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
- shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
- componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
# 在这里着重讲一讲updation部分:(也就是组件发生更新的时候)
组件发生更新的生命周期,又分为两个部分,props
和states
:
记住:组件第一次存在dom中函数不会被执行,如果已经存在于dom中,函数才会被执行
# state更新部分:
需要在更新this.setState
的前后进行触发:
会先后触发shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate。
shouldComponentUpdate(nextProps,nextState)
:- 顾名思义“组件是否被更新”。需要必须返回一个布尔值
- true为setState之后改变state,然后会触发render改变视图
- false为setState之后也改变state,但是不触发render改变视图
- componentWillUpdate:
- 在render之前,shouldComponentUpdate之后执行
- render:
- 重新渲染视图
- componentDidUpdate:
- render渲染结束时执行
# prop更新部分:
会先后触发componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
- componentWillReceiveProps:
- 当组件所接收的props的值被改变的时候,才会触发执行
- (组件第一次存在dom中函数不会被执行,如果已经存在于dom中,函数才会被执行)
- 后面的生命周期和
state
是一致的
# 通过shouldComponentUpdate优化性能:
父组件中的state或props改变触发render,但是又频繁的render了父组件中的子组件,性能很不好。
- 在子组件中的shouldComponentUpdate生命周期中进行判断
shouldComponentUpdate(nextProps,nextState)
class Child extends React.Component{
shouldComponentUpdate(nextProps,nextState){
if(nextState.open!==this.state.open){
return true
}
if(nextProps.color!==this.props.color){
return true
}
return false
}
state={
open:'opened'
}
}