简介
React用于动态构建用户界面的JavaScript库(只关注与视图,由Facebook开源)
特点
声明式编码;组件化编码;React Native编写原生应用;高效(优秀的Diffing算法)
高效原因:
1)使用虚拟(virtual)DOM,不总是直接操作页面真实DOM
2) DOM Diffing算法,最小化页面重绘
JSX:
1)全称:JavaScript XML
2)react定义的一种类似于XML的JS扩展语法:JS+XML
3)本质是React.createElement(component,props,...children)方法的语法糖
4)作用:用来简化创建虚拟DOM
a.写法:var ele = (<h1 id="title"><span>Hello JSX!</span></h1>)
b.注意1:它不是字符串,也不是HTML/XML标签
c.注意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() 4)arr.map() 5)function test(){}
2.语句(代码)
1)if(){} 2)for(){} 3)switch(){case:xxx}
虚拟DOM:
1)本质是object类型的对象(一般对象)
2)虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性;
3)虚拟DOM最终会被React转化为真实DOM,呈现在页面上
创建函数式组件:
//类型是babel,babel将jsx语句转换为js语句
<script type="text/babel">
//1.创建函数式组件
function MyComponent(){
console.log(this);
//此处的this是undefined,因为babel编译之后开启了严格模式,严格模式下禁止自定义函数里面的this指向window,因此是undefined
return <h2>我是组件</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
执行了ReactDOM.render(<MyComponent/>.....之后,发生了什么?)
1、React解析组件标签,找到了MyComponent组件
2、发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面上
注意:
由于创建函数式组件之后,经过babel编译开启了严格模式,严格模式下禁止自定义函数里面的this指向window,因此这里函数中的this是undefined,从而不能使用react原型链上的state\ref等方法属性,反而类式组件是可以继承react原型链上的方法属性;只不过在最新版本的react中,官方新增了hooks方法,可以令函数式组件也能够使用react原型链上的方法属性。
复习一:类的基本知识:
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}`)
}
//创建一个Student类,继承于Person类
class Student extend Person{
constructor (name,age,grade){
super(name,age)
this.grade=grade
this.school="家里蹲大学"
}
//类中可以直接写赋值语句,如下代码含义是:给Student的实例对象添加一个属性,名为a,值为1.
a = 1
//给类的实例加上一个属性
static demo = 100
speak(){
console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`)
}
study(){
//study方法放在哪里--类的原型对象上,供实例使用
//通过Student实例调用study时,study中的this就是Student实例
console.log("哈哈哈")
}
}
const s1 = new Student('小张',15,"高一")
console.log(s1)
s1.speak()
s1.study()
总结:
1、类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
2、如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
3、类中所定义的方法,都是放在了类的原型对象上。
复习二:类中的方法this指向
class Person {
constructor(name,age){
this.name = name
this.age = age
}
study(){
//study方法放在了哪里?————类的原型对象上,供实例使用
//通过Person实例调用study时,study中的this就是Person实例
console.log(this);
}
}
//方式一:
const p1 = new Person('tom',18)
p1.study()//通过实例调用study方法,输出的this为person的实例对象
//方式二:
const x = p1.study//直接将函数赋值给x
x()//x直接调用输出的this为undefined
创建类式组件:
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,随后呈现在页面上
复习三:原生绑定事件(三种方式)
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick = "demo()">按钮3</button>
<script>
const btn1 = document.getElementById("btn1")
btn1.addEventListener('click' ,()=>{
alert("按钮1被点击了")
})
const btn2 = document.getElementById("btn2")
btn2.onclick = ()=>{
alert("按钮2被点击了")
}
function demo(){
alert("按钮3被点击了")
}
</script>
组件实例三大属性:1、State
1)理解:
state是组件对象中最重要的属性,值是对象(可以包含多个key-value的组合);
组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)。
2)强烈注意:
组件中render方法中的this为组件实例对象;
组件自定义的方法中this为undefined,如何解决?
a.强制绑定this:通过函数对象的bind();
b.赋值语句+箭头函数;
状态数据:不能直接修改或更新,需要调用原型上的this.setState方法进行调用。
//1、创建代码
class Weather extends React.Component{
//构造器调用几次?--1次
constructor(props){
super(props)
//初始化状态
this.state = {isHot:false}
//解决this指向问题方法一:
this.demo= this.changeWeather.bind(this)
//bind方法可以生成一个新函数,新函数绑定的this是实例对象,后续onClick调用的回调函数是通过bind生成的新函数
}
//render调用几次?--1+n次,1是初始化那次,n是状态更新的次数
render(){
//读取状态
const {isHot} = this.state
return<h1 onClick={this.demo}>今天天气很{isHot?'炎热':'凉爽'}</h1>
}
//changeWeather调几次?--点几次调几次
changeWeather(){
//changeWeather放在哪里?————Weather的原型对象上,供实例使用
//通过Weather实例调用changeWeather时,changeWeather中的thia就是Weather的实例
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
//严重注意:状态state不可以直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2、渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
精简后代码:
//1、创建代码
class Weather extends React.Component{
//类中可以直接写赋值语句;
//初始化状态
state = {isHot:false}
render(){
const {isHot} = this.state
return<h1 onClick={this.demo}>今天天气很{isHot?'炎热':'凉爽'}</h1>
}
//使用赋值语句,直接将changeWeather放在实例自身;箭头函数没有自己的this,它会自己通过层级关系去自己的最近外层找到this的指向
//自定义方法--要用赋值语句加箭头函数的形式
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
console.log(this)
}
}
//2、渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
组件实例三大属性:2、Props
1)理解:
每个组件对象都会有props(properties的简写)属性;
组件标签的所有属性都保存在props中
2)作用:
通过标签属性从组件外向组件内传递变化的数据;
注意:组件内部不要修改props数据
3)编码写法:
一般写法:
//创建组件
class Person extend React.Component{
render(){
console.log(this);
const(name,age,sex) = this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="haha" age="18" sex="男"/>,document.getElementById('test1'))
精简写法:
//创建组件
class Person extend React.Component{
render(){
console.log(this);
const(name,age,sex) = this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
//渲染组件到页面
const p = {name:"haha",age:"18",age:"男"}
console.log(...p)//空值,无法展开
ReactDOM.render(<Person {...p}/>,document.getElementById('test1'))
//这里由于react和babel双重加持下,可以允许使用...展开一个对象;但是只能用在标签传递中,别的地方都不行
复习四:展开运算符
使用场景1:
let arr1 = [1,3,5,7]
let arr2 = [2,4,6,8]
console.log(...arr1)//展开一个数组
let arr3 = [...arr1,...arr2]//连接数组
使用场景2:
//在函数中使用
function sum(...number){
//函数的参数可以接到一个number的数组,数组上有一个求和的方法reduce,接收到两个参数,第一个参数是之前的值,第二个参数是当前的值
return numbers.reduce((preValue,currentValue)=>{
return preValue + currentValue
})
}
console.log(sum(1,2,3,4))
使用场景3:
//构造字面量对象时使用展开语法
let person = {name:"tom",age:18}
console.log(...person);//报错,展开运算符不能展开对象
let person2 = {...person}//解构赋值,克隆了一个对象
person.name = "1"
console.log(person2)//name不会改变
console.log(person)//name发生改变
使用场景4:
//合并
let person3 = {...person,name:"222"}
console.log(person3)//name发生改变
对props的限制
//创建组件
class Person extend React.Component{
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 + 1}</li>
</ul>
)
}
}
//对props进行规则限制
Person.propTYpes = {
name:PropTypes.string.isRequired,//属性类型和必要性限制
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func,//限制为函数类型
}
//默认值
Person.defaultProps = {
sex:"男",
age:18,
}
//渲染组件到页面
const p = {name:"haha",age:18,age:"男"}
ReactDOM.render(<Person {...p}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="haha" age={18} sex="男" speak={speak}/> document.getElementById('test1'))
function speak(){}
props精简写法
//创建组件
class Person extend React.Component{
//对props进行规则限制
static propTYpes = {
name:PropTypes.string.isRequired,//属性类型和必要性限制
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func,//限制为函数类型
}
//默认值
static defaultProps = {
sex:"男",
age:18,
}
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 + 1}</li>
</ul>
)
}
}
//渲染组件到页面
const p = {name:"haha",age:18,age:"男"}
ReactDOM.render(<Person {...p}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="haha" age={18} sex="男" speak={speak}/> document.getElementById('test1'))
function speak(){}
函数式组件中使用props
//创建组件
function Person(props) {
console.log(props);
const(name,age,sex) = props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//对props进行规则限制
Person.propTYpes = {
name:PropTypes.string.isRequired,//属性类型和必要性限制
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func,//限制为函数类型
}
//默认值
Person.defaultProps = {
sex:"男",
age:18,
}
//渲染组件到页面
ReactDOM.render(<Person name="haha" age={18} sex="男"/> document.getElementById('test1'))
构造器作用:
1)通过给this.state赋值对象来初始化内部state
2)为事件处理函数绑定实例
注意:
构造器可写可不写,如果写了必须要接props,并且要用super进行调用props,否则this.props是undefined;构造器是否接收props,是否传递props,取决于是否希望在构造器中通过this访问props
组件实例三大属性:3、refs和事件处理
1)理解:组件内的标签可以定义ref属性来标识自己
2)编码:
字符串形式的ref
//创建组件
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'))
回调函数形式的ref
//创建组件
class Demo extends React.Component{
state = {isHot:false}
//展示左边的输入框数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右边的输入框数据
showData2 = ()=>{
const{input2} = this
alert(input2.value)
}
//切换天气
changeWeather = ()=>{
//获取原来的状态
const {isHot} = this.state
//更新状态
this.setState({isHot:!isHot})
}
render(){
return(
const {isHot} = this.state
<div>
<h1>今天天气很{isHot ? "凉爽" : "炎热"}</h1>
<button onClick={this.changeWeather}>点我切换天气</button>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a='1' b='2'/>,document.getElementById('test'))
注意:如果ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素,这是因为每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的,通过将ref的回调函数定义成class的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的,即可以忽略。
//创建组件
class Demo extends React.Component{
state = {isHot:false}
//展示左边的输入框数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右边的输入框数据
showData2 = ()=>{
const{input2} = this
alert(input2.value)
}
//切换天气
changeWeather = ()=>{
//获取原来的状态
const {isHot} = this.state
//更新状态
this.setState({isHot:!isHot})
}
saveInput = (c)=>{
this.input1 = c
}
render(){
return(
const {isHot} = this.state
<div>
<h1>今天天气很{isHot ? "凉爽" : "炎热"}</h1>
<button onClick={this.changeWeather}>点我切换天气</button>
//jsx中的注释:
{/*<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/> */}
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a='1' b='2'/>,document.getElementById('test'))
createRef
//创建组件
class Demo extends React.Component{
//React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”
myRef = React.createRef()
myRef2 = React.createRef()
//展示左边的输入框数据
showData = ()=>{
alert(this.myRef.current.value)
}
//展示右边的输入框数据
showData2 = ()=>{
const{input2} = this.refs
alert(this.myRef2.current.value)
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a='1' b='2'/>,document.getElementById('test'))
事件处理
1)通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件--为了兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)--为了高效
2)通过event.target得到发生事件的DOM元素对象--发生事件的元素正好是操作的元素,可以避免使用ref,不要过度使用ref.
//创建组件
class Demo extends React.Component{
//展示右边的输入框数据
showData2 = (event)=>{
alert(event.target.value)
}
render(){
return(
<div>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a='1' b='2'/>,document.getElementById('test'))
收集表单数据
包含表单的组件分类:
a.非受控组件
//创建组件
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'))
b.受控组件(推荐使用)
//创建组件
Class Login extends React.Component{
//初始化状态
this.state = {
username:''
password:''
}
//保存用户名到状态中
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
//保存密码到状态中
saveUsername = (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'))
高阶函数_函数柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数:
1)若A函数,接收的参数是一个函数,那该函数就是高阶函数;
2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数;
常见的高阶函数有:Promise、setTimeout、arr.map()等
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function(a,b,c){
return a+b+c
}
const x = function(1,2,3)
//函数柯里化后
function(a){
return(b){
return(c){
return a+b+c
}
}
}
const result = function(1)(2)(3)