component - class
- 知识回顾(类的继承)
01 super(props) super有参数
class A{
constructor(n){
//this指向B的实例
this.x = n
}
}
class B extends A{
constructor(props){
super(props)
//给B实例添加私有属性
this.y = 100
}
}
let b = new B(100)
console.log(b.x);
console.dir(b);
02 super( )无参数时
- 定义类组件:有自己的状态 和 生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 挂载容器 -->
<div id="app"></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">
class Welcome extends React.Component{
constructor(props){
console.log(props); //{name:"tina"}
super(props) //调用super时,该类组件的父类会执行this.props = props
}
//rend必须写,渲染时会自动被调用 组件内容,通过返回值返回
render(){
console.log(this); //this指向当前类的实例
console.log(this.props);//{name:"tina"}
return <h1>Hello World!</h1>
}
}
const e = <Welcome name = "tina" />
console.log(e);
//04 虚拟DOM 伪代码
// {
// type:f Welcome(props),
// props:{name:'tina'}
// }
//虚拟DOM => type判断(如果是类组件,new调用new Welcome(props))=>真实DOM => 渲染到容器里面
ReactDOM.render(e,document.getElementById("app"));
</script>
</body>
</html>
注意:
即使不在constructor中调用super(props),render中依然可以使用this.props,因为render可以帮我们做;
那么,在constructor中调用super(props)有什么意义呢?
在constructor中调用super(props),可以在constructor阶段直接使用this.props
例如:super()不传参数,在constructor阶段中打印this.props结果为undefined
- 时钟案例
01 函数组件(手动)
//函数组件:时钟
function Clock(props){
return (
<div>
<h1>Clock</h1>
<h2>{props.time}</h2>
</div>
)
}
function tick(){
var e = <Clock time={new Date().toLocaleTimeString()}/>
ReactDOM.render(e,document.getElementById("app"));
}
//手动:每隔一秒钟 波动一次秒针
setInterval(tick,1000)
02 函数组件转化为类组件(手动)
class Clock extends React.Component{
render(){
console.log(this.props);
return(
<div>
<h1>Clock</h1>
<h2>{this.props.time}</h2>
</div>
)
}
}
function tick(){
var e = <Clock time={new Date().toLocaleTimeString()}/>
ReactDOM.render(e,document.getElementById("app"));
}
//手动:每隔一秒钟 波动一次秒针
setInterval(tick,1000)
注意:
当我们在render中打印this.props的时候,会发现它会一直重复打印,这是为什么呢?
——这是因为render具有该特点: 每次props变化,render会重新执行,页面会重新渲染;
03 自动化时钟(维护状态state)
当我们在componentDidMount(){}钩子函数中,用this.state.time=""改变数据时,会发现,打印出的数据被成功修改,但是页面并没有更新,这是react不同于vue之处,它不具备双向绑定。
解决方法:强制更新
(1)使用forceUpdate()强制刷新,每次刷新都会重新执行
(2)使用this.setState()也会重新执行(推荐使用)
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
//n:10
}
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
//点击按钮销毁div(app)
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
// <h2>{this.state.n}</h2>
</div>
)
}
//第一次渲染完毕,会执行该钩子函数
componentDidMount(){
console.log('componentDidMount');
// this.state.time='abc'
// console.log(this.state);
// this.forceUpdate()
this.timerId=setInterval(()=>{
this.setState({time:new Date().toLocaleTimeString()})
},1000)
//修改n,发现并未改变,因为setState 在这时是异步的
//解决方法:回调函数
// this.setState({
// n:13
// },()=>{
// console.log("callback",this.state);
// })
//console.log(n);
}
//第一次渲染完毕,会执行该钩子函数
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
- 类组件的this指向问题
01 生命周期中的钩子函数中的this - -> 指向当前类组件的实例(如:componentDidMount,render)
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
}
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
console.log(this);
this.timerId=setInterval(()=>{
this.setState({time:new Date().toLocaleTimeString()
})
},1000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
02 函数的this --> 指向undefined
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
}
}
go(){
console.log(this); //undefined
this.setState({
time : new Date().toLocaleTimeString(),
})
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={this.go}>GO1</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
03 this.go 与 this.go()
this.go --> 表示引用 (this指向undefined);
this.go()–> 找到函数,直接执行(报错,会导致内存溢出<–重复调用render和执行函数–死循环,this指向调用者);
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
}
}
go(){
console.log(this);
this.setState({
time : new Date().toLocaleTimeString(),
})
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={this.go()}>GO2</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
04 关于函数this指向问题的解决方法
(1)使用箭头函数:箭头函数中的this指向该箭头函数所在的外围作用域,即render,render --> 指向当前类组件的实例;
*问题:
- 函数是一个特殊对象,需要占用内存空间;每次调用render()都i相当于重复声明 => 重复开辟内存空间,性能相对较差;
- jsx元素构建中添加js逻辑代码,导致代码复杂、臃肿,不好维护
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
}
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={
()=>{
this.setState({
time : new Date().toLocaleTimeString(),
})
}
}>GO3</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
(2)手动改变this指向,使用bind进行绑定;
*注意:该方法虽然解决了代码逻辑混乱问题,但是每次重新绑定都会生成新的函数,依然没有解决重复开辟内存空间,性能相对较差的问题
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {
time : new Date().toLocaleTimeString(),
}
}
go(){
console.log(this); //指向当前类组件的实例
this.setState({
time : new Date().toLocaleTimeString(),
})
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={this.go.bind(this)}>GO4</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
(3)推荐方式:在constructor构造函数中,一次性绑定this,在render中直接通过this.go进行调用
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {time : new Date().toLocaleTimeString()}
//一次性绑定this
console.log(this);
//go函数 => 生成新的函数(新的函数已经绑定this) => 新的函数赋值给this.go属性上
this.go = this.go.bind(this)
}
go(){
console.log("GO5");
this.setState({
time : new Date().toLocaleTimeString(),
})
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={this.go}>GO5</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
(4)ES7新的函数写法go=()=>{},不需要考虑this , 可以在render中直接通过this.go进行调用
class Clock extends React.Component{
constructor(props){
super(props)
this.state = {time : new Date().toLocaleTimeString()}
//一次性绑定this
console.log(this);
//go函数 => 生成新的函数(新的函数已经绑定this) => 新的函数赋值给this.go属性上
this.go = this.go.bind(this)
}
go=()=>{
console.log(this);
this.setState({
time : new Date().toLocaleTimeString(),
})
}
render(){
return(
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
<button onClick={this.go}>GO6</button>
<button onClick={
()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove Component</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
}
componentWillUnmount(){
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />,document.getElementById("app"));
- setState方法
01 状态初始化的两种方式
<script type="text/babel">
class App extends React.Component{
constructor(props){
super(props)
//状态初始化
this.state = {a:0}
}
//状态初始化
state1 = {b:0,c:0}
//必须实现
render(){
return (
<div>
<h1>a:{this.state.a}</h1>
<h1>b:{this.state1.b}</h1>
<h1>c:{this.state1.c}</h1>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
02 可以直接修改数据,但是不能触发重新渲染render
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
//直接修改数据
this.state.a=1
console.log(this.state.a);
}
//必须实现
render(){
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
03 setState()修改数据 + 触发render
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
//setState()修改数据 + 触发render
this.setState({
a:1
})
console.log(this.state.a);
}
//必须实现
render(){
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
//第一次挂载完毕
componentDidMount(){
//生命周期钩子函数里,setState是异步的
this.setState({
b:1
})
console.log(this.state.b);
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
注意:
- 当我们打开页面的时候,页面中的b直接变成了1,打印的结果是0,这是因为setState在生命周期钩子函数中是异步的,先执行console.log(),再执行this.setState();
- 当我们第一次点击按钮的时候,会发现页面中a变成了1,而打印时却是0,这是因为setState在合成事件(handleClick)里面是异步的
- 总结:
setState是异步的条件:1)在合成事件中 2)在生命周期钩子函数中
04 多次调用setState
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
this.setState({a:1})
this.setState({b:1})
this.setState({c:1})
console.log(this.state);
}
//必须实现
render(){
console.log('render');
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
//第一次挂载完毕
componentDidMount(){
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
- 打印this.state结果为{a: 0, b: 0, c: 0},因为setState是异步的
- 点击按钮后,三次setState,只打印一次render <=react性能优化 state数据全部被修改,(批量更新)
- react性能优化,批量更新的时机是在render之前的某个时候,setState都已经更新完才会打印render
05 累加
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
this.setState({a:1}) //a:1 不立即执行
this.setState({a:this.state.a+1})//a:0+1=1 不立即执行
this.setState({a:this.state.a+1})//a:0+1=1 不立即执行
}
//必须实现
render(){
console.log('render');
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
//第一次挂载完毕
componentDidMount(){
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
- 点击按钮后,页面中a变成了1,并没有进行多次累加=>因为setState是异步的,不是立即执行的;
- 执行机制如下:
this.setState({a:1}) //a:1 不立即执行
this.setState({a:this.state.a+1})//a:0+1=1 不立即执行
this.setState({a:this.state.a+1})//a:0+1=1 不立即执行
//发现都为a:1,所以进行合并,转换为
this.setState({a:1})
因此最后a为1- 隐患:调用setState更新的值,依赖于state里面的值,不能依赖于上一个状态去计算下一个状态
06 解决05的累加隐患问题
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
this.setState((preState,props)=>{return{a:preState.a + 1}})
//简写
this.setState((preState,props)=>{a:preState.a + 1})
this.setState((preState,props)=>{a:preState.a + 1})
this.setState((preState,props)=>{a:preState.a + 1})
}
//必须实现
render(){
console.log('render');
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
//第一次挂载完毕
componentDidMount(){
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
- setState 接收一个函数
- 函数的第一个参数preState=>代表上一个状态
- 函数的返回值也是一个对象,是新的状态,可以依赖于上一个状态做计算- 并未解决异步问题
点击按钮a由之前的0,变成了1,再次点击变成2,只实现了依赖上一次的结果计算;而代码中有四个setState,并没有累加4,因此没有解决异步问题
07 解决setState在合成事件中的异步问题
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
this.setState((preState,props)=>({a:preState.a + 1}),()=>{
console.log('callback',this.state.a);
})
}
//必须实现
render(){
console.log('render');
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
//第一次挂载完毕
componentDidMount(){
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
this.setState((preState,props)=>({a:preState.a + 1}),()=>{
console.log(‘callback’,this.state.a);
})
通过第二个参数,通过回调函数获取state的真实值
08 定时器中的setState是同步的
<script type="text/babel">
class App extends React.Component{
//状态初始化
state = {a:0,b:0,c:0}
handleClick=()=>{
console.log(this); //箭头函数=>this指向handleClick作用域,this指向组件实例
setTimeout(()=>{
this.setState({a:1})
this.setState({a:this.state.a+1})
this.setState({a:this.state.a+1})
},1000)
}
//必须实现
render(){
console.log('render');
return (
<div>
<h3>a:{this.state.a}</h3>
<h3>b:{this.state.b}</h3>
<h3>c:{this.state.c}</h3>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
- 点击一次按钮,a变成了3,可见定时器中的setState是同步的,并且不会发生合并
- 每次render都会执行,不会批量更新,没有状态依赖问题
- 异步问题
01 同步
function add(a,b){
return a + b
}
console.log(add(1,1));
02 含有异步代码
function sum(a,b){
//setTimeout是异步,先不执行;执行函数默认返回值undefined
setTimeout(()=>{
return a + b
},1000)
// return undefined
}
console.log(sum(1,1));
03 解决异步问题(1)callback
- 回调函数callback(在函数的参数中传递一个函数,并在指定的时机去执行)
- 问题:会导致回调地狱,不停的嵌套回调函数
function sumer(a,b,callback){
setTimeout(()=>{
let result = a + b
callback(result)
// return a + b
},1000)
}
function callback(result){
console.log(result);
}
// sumer(1,2,callback)
let r = sumer(1,2,callback);
console.log(r);//返回值没有意义
04 解决异步问题(2)封装promise–>解决了回调地狱的问题
function sumers(a,b){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
let result = a + b
resolve(result)
},1000)
})
}
sumers(10,10)
.then((result)=>{
console.log(result);
})
.catch((err)=>{
console.log(err);
})
- setState原理模拟
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 挂载容器 -->
<div id="app"></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">
class App extends React.Component{
constructor(props){
super(props)
this.state = {name:'zhangsan',age:18}
this.state1 = {name:'zhangsan',age:18}
}
handleClick1 = ()=>{
this.setState1({name:'lisi'})
}
setState1 = (newState)=>{
//1.修改数据
//{}=>{name:'zhangsan',age:18} + {name:'lisi'} => {name:'lisi',age:18}
//Object.assign(target,...source) 源对象=>目标对象
//(1)合并数据
let obj = Object.assign({},this.state1,newState) //{name:'lisi',age:18}
//(2)替换属性
this.state1 = obj
//2.重新渲染
this.forceUpdate()
}
render(){
return(
<div>
<h2>state name:{this.state.name}</h2>
<h2>state1 name:{this.state1.name}</h2>
<button onClick={this.handleClick1}>button1</button>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById("app"));
</script>
</body>
</html>