探讨H5 UI渲染心智模型,即阐述数据是依据什么样的逻辑渲染到界面上的。
先通过一个示例讲述不同的数据渲染逻辑,然后讲两个延伸DEMO来着重说明CLASS和FUNCTION的特点;
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React Apptitle>
head>
<body>
<div id="root" />
<script type="text/javascript" src="./index.js">script>
body>
html>
index.js
(() => {
// 获取用于挂载数据的元素标签
const root = document.getElementById('root')
setInterval(() => {
// 获取当前系统时间
const currentTime = new Date( +new Date() + 8 * 3600 * 1000 ).toJSON().slice(0,19).replace("T"," ")
// 将系统时间挂载到元素标签上
root.innerText = "当前系统时间是:" + currentTime
},1000)
})()
DATA CHNAGE => 生成新的虚拟DOM,比较与旧DOM 之间的DIFF => DOM CHANGE
带有实例this和生命周期(创建,更新,销毁等周期)的组件,组件所有内容(包括数据)都挂在this上,通过this可以获取的组件的最新状态。
import React from 'react';
class ProfilePage extends React.Component {
state={
time: ''
}
componentDidMount() {
setInterval(() => {
// 获取当前系统时间
const currentTime = new Date( +new Date() + 8 * 3600 * 1000 ).toJSON().slice(0,19).replace("T"," ")
// 将系统时间赋值给state
this.setState({
time: currentTime
})
},1000)
}
render() {
return <div>{this.state.time}</div>;
}
}
export default ProfilePage;
没有实例和生命周期的纯函数组件,没有this,数据由hooks维护,可使用Hooks模拟生命周期特性;
Hooks 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。
在这里,useState 就是一个 Hook。通过在函数组件里调用它来给组件添加一些内部 state。
a,你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
b,useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的触发时机,只不过被合并成了一个 API。
c,当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
// 触发时机等于componentDidMount+componentDidUpdate
useEffect(() => {
// do some thing
})
// 添加依赖后,触发时机等于componentDidMount
useEffect(() => {
// do some thing
},[])
// 添加return后,,return的触发时机等于componentWillUnmount
useEffect(() => {
// do some thing
return () => {
// 触发时机等于componentWillUnmount
}
}, [])
import React, {useState, useEffect} from 'react';
function ProfilePage() {
const [time, changeTime] = useState('')
useEffect(() => {
setInterval(() => {
// 获取当前系统时间
const currentTime = new Date( +new Date() + 8 * 3600 * 1000 ).toJSON().slice(0,19).replace("T"," ")
// 将系统时间赋值给state
changeTime(currentTime)
},1000)
},[])
return (
<div>{time}</div>
);
}
export default ProfilePage;
import React from "react";
import ProfilePageFunction from './ProfilePageFunction';
import ProfilePageClass from './ProfilePageClass';
class App extends React.Component {
state = {
user: '凌云',
};
render() {
return (
<>
<label>
<b>选择你想浏览的主页: </b>
<select
value={this.state.user}
onChange={e => this.setState({ user: e.target.value })}
>
<option value="凌云">凌云</option>
<option value="晓林">晓林</option>
<option value="江江">江江</option>
</select>
</label>
<h1>欢迎来到 {this.state.user}的 个人主页!</h1>
<p>
<ProfilePageFunction user={this.state.user} />
<b> (function组件)</b>
</p>
<p>
<ProfilePageClass user={this.state.user} />
<b> (class组件)</b>
</p>
</>
)
}
}
export default App;
import React from 'react';
class ProfilePage extends React.Component {
showMessage = () => {
alert('已关注:' + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return <button onClick={this.handleClick}>关注</button>;
}
}
export default ProfilePage;
import React from 'react';
function ProfilePage(props) {
const showMessage = () => {
alert('已关注: ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>关注</button>
);
}
export default ProfilePage;
在凌云的主页点击关注,然后在3秒内切换到晓林的主页。
此时,function组件正常;
class组件异常,3秒后,提示关注了晓林。
CLASS组件数据挂载在this上,this是在时刻变化的。
import React from 'react';
class ProfilePage extends React.Component {
showMessage = (user) => {
alert('已关注:' + user);
};
handleClick = () => {
const user = this.props.user
setTimeout(() => {
this.showMessage(user)
}, 3000);
};
render() {
return <button onClick={this.handleClick}>关注</button>;
}
}
export default ProfilePage;
import React from "react";
import ProfilePageFunction from './ProfilePageFunction';
import ProfilePageClass from './ProfilePageClass';
class App extends React.Component {
render() {
return (
<>
<div>
<b> (function)</b>
<ProfilePageFunction />
</div>
<div>
<b> (class)</b>
<ProfilePageClass />
</div>
</>
)
}
}
export default App;
import React from 'react';
class ProfilePage extends React.Component {
state={
count: 0
}
componentDidMount() {
setInterval(() => {
// 组件挂载时开启定时器,一秒加一
this.setState({
count: this.state.count + 1
})
},1000)
}
render() {
return <div>{this.state.count}</div>;
}
}
export default ProfilePage;
import React, {useState, useEffect} from 'react';
function ProfilePage() {
const [count, changeCount] = useState(0)
useEffect(() => {
setInterval(() => {
// 组件挂载时开启定时器,一秒加一
changeCount(count + 1)
},1000)
}, [])
return (
<div>{count}</div>
);
}
export default ProfilePage;
CLASS实现正常,
FUNCTION实现异常:界面从0到1之后,就不走了
添加 [ ] 依赖后的useEffect的确在触发时机上和componentDidMount一样,只会在function组件第一次渲染的时候执行一次,后面不再执行,但是它因为没有this,所以这里面拿到的count永远都是初始count=0;
useEffect(() => {
setInterval(() => {
// 组件挂载时开启定时器,一秒加一
changeCount(count + 1)
},1000)
}, [])
import React, {useState, useEffect, useRef } from 'react';
function ProfilePage() {
const [count, changeCount] = useState(0)
const coutRef = useRef(0)
const startAdd = () => {
setInterval(() => {
// 一秒加一
coutRef.current = coutRef.current + 1
changeCount(coutRef.current)
},1000)
}
useEffect(() => {
// 组件挂载时开启定时器
startAdd()
}, [])
return (
<div>{count}</div>
);
}
export default ProfilePage;
1, Dan Abramov博客: 函数式组件与类组件有何不同?
https://overreacted.io/zh-hans/how-are-function-components-different-from-classes/
2,react官方文档:使用Effect Hook
https://react.docschina.org/docs/hooks-effect.html