【问题探讨】H5 UI渲染心智模型

目标

探讨H5 UI渲染心智模型,即阐述数据是依据什么样的逻辑渲染到界面上的。

本文思路

先通过一个示例讲述不同的数据渲染逻辑,然后讲两个延伸DEMO来着重说明CLASS和FUNCTION的特点;

示例,实现如下一个时钟,页面加载时开始1s跳动一次

时钟

实现

一,原生JS直接操作DOM

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <!-- 写一个div元素标签用于挂载数据 -->
    <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)
})()
二,通过框架(vue、react、angular等)实现DATA => DOM同步

DATA CHNAGE => 生成新的虚拟DOM,比较与旧DOM 之间的DIFF => DOM CHANGE

1.1,CLASS组件的特点

带有实例this和生命周期(创建,更新,销毁等周期)的组件,组件所有内容(包括数据)都挂在this上,通过this可以获取的组件的最新状态。
在这里插入图片描述

1.2,CLASS组件实现时钟
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;

2.1,FUNCTION组件的特点

没有实例和生命周期的纯函数组件,没有this,数据由hooks维护,可使用Hooks模拟生命周期特性;

什么是 Hooks?

Hooks 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

什么是useState?

在这里,useState 就是一个 Hook。通过在函数组件里调用它来给组件添加一些内部 state。

什么是useEffect?

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
 	}
  }, [])
2.2,FUNCTION组件实现时钟
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;

三,延伸DEMO
1,关注demo

点击关注,关注某个人的主页
在这里插入图片描述

1.1 公共代码
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;


1.2,CLASSS实现
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;

1.3,FUNCTION实现
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;

1.4,正常情况下没问题,做一个特殊操作

在凌云的主页点击关注,然后在3秒内切换到晓林的主页。
此时,function组件正常;
class组件异常,3秒后,提示关注了晓林。

原因就是上面讲的:

CLASS组件数据挂载在this上,this是在时刻变化的。

处理方式1(其他处理方式不再展开),在发起关注时缓存关注的user
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;

2,自增DEMO

实现一个一秒加一的自增计数功能
在这里插入图片描述

2.1, 公共代码
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;
2.2 CLASS实现
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;

2.3 FUNCTION实现
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;

2.4,运行后发现

CLASS实现正常,
FUNCTION实现异常:界面从0到1之后,就不走了

原因是:

添加 [ ] 依赖后的useEffect的确在触发时机上和componentDidMount一样,只会在function组件第一次渲染的时候执行一次,后面不再执行,但是它因为没有this,所以这里面拿到的count永远都是初始count=0;

  useEffect(() => {
    setInterval(() => {
      // 组件挂载时开启定时器,一秒加一
      changeCount(count + 1)
    },1000)
  }, [])
处理方式1(其他方式不再展开),使用 useRef 来跨越渲染周期存储数据(但是它和 useState 的区别,除了可以跨越渲染周期存储数据,同时对它修改也不会引起组件重新渲染)
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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tom_wong666

码字不易,分享有功,期待赞赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值