React基本语法与注意事项

216 阅读15分钟

React官网

介绍描述

  • 用于动态构建用户界面的 JavaScript 库(只关注于视图)
  • 由Facebook开源

声明式编码

  • 命令式编码

    • document.body.style.background = '#ccc'
  • 声明式编码

    • this.setState({bg: '#ccc'})

React的特点

  • 声明式编码
  • 组件化编码
  • 高效(优秀的Diffing算法)
  • 引入 jsx 语法
  • React Native 编写原生应用

React高效的原因

  • 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  • DOM Diffing算法, 最小化页面重绘。

相关js库

  • react.js:React核心库。
  • react-dom.js:提供操作DOM的react扩展库。
  • babel.min.js:解析JSX语法代码转为JS代码的库。

虚拟DOM与真实DOM

  • React提供了一些API来创建一种 “特别” 的一般js对象
const VDOM = React.createElement('xx',{id:'xx'},'xx')
上面创建的就是一个简单的虚拟DOM对象
  • 虚拟DOM对象最终都会被React转换为真实的DOM
  • 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

jsx

  • 全称: JavaScript XML,是react定义的一种类似于XML的JS扩展语法,其本质是React.createElement(component, props, ...children)方法的语法糖
  • 作用: 用来简化创建虚拟DOM
​
写法:var ele = <h1>Hello JSX!</h1>
​
//JSX 注意点
1. 它不是字符串, 不需要添加引号,它最终产生的就是一个JS对象,不能用引号包裹, 可以用『小括号』包裹
2. 结构中混入 js 表达式需要使用  {}    JSX 与『JS表达式』拼接时, 需要用到 『{}』 
3. 设置 class 属性时需要使用 className
4. 指定内联样式时,需要使用对象的形式   style={{color:'red'}}
5. 所有结构必须要有根标签
6. 所有标签必须要闭合,对于自闭和标签  img  input 注意,也必须要闭合  <img></img>  <img /> 两种写法都可以
7. 标签名
    1)  若首字母为小写,则将其转化为 html 同名标签。若找不到同名标签则报错
    2)  若首字母为大写,则回寻找对应的组件,找到就渲染,找不到就报错
babel.js的作用
    如果编写的是 HTML 标签, 标签名首字母一定要『小写』, 如果编写的是组件标签, 标签名首字母一定要『大写
​
    1)  浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
    2)  只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
    

关于 {} 中能放的内容

JS 的表达式. (有返回值)

  1. 四则运算 1 + 1 2*2 3/3 4-3 %
  2. 变量 let b = a;
  3. 三元表达式
  4. 函数调用 function fn(){} fn();
  5. 直接量

{} 中放置的表达式, 可以返回 『数字』 『字符串』 『虚拟DOM』

渲染虚拟DOM(元素)

  • 1.  语法:  ReactDOM.render(virtualDOM, containerDOM)
    
  • 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

  • 参数说明

    参数一: 纯js或jsx创建的虚拟dom对象

    参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

条件渲染

let isVip = true; // is 是否  vip 贵宾//声明一个函数
function showAd(){
    if(isVip){
        return null;
    }else{
        return <div id="ad"></div>;
    }
}
​
//创建虚拟 DOM   
let vDOM = <div>
        {/*1. 第一种 &&*/}
        {!isVip && <div id="ad"></div>}
        {/*2. 第二种 三元表达式*/}
        {isVip ? null : <div id="ad"></div>}
        {/*3. 第三种 函数调用*/}
        {showAd()}
        {<div>abc</div>}
    </div>;
​
//渲染
ReactDOM.render(vDOM, document.querySelector("#root"));

列表渲染

//准备数据
let data = ['vue', 'react', 'angular'];
​
// let arr = [<span>123</span>, <span>456</span>, <span>789</span>];//创建虚拟 DOM
let vDOM = <div>
    <h2>前端三大框架</h2>
    <ul>
        {
            data.map((item, index) => {
                return <li key={index}>{item}</li>;
            })
        }
    </ul>
</div>;
​
ReactDOM.render(vDOM, document.querySelector("#root"));

模块与组件、模块化与组件化的理解

模块

  1. 理解:向外提供特定功能的js程序, 一般就是一个js文件
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  3. 作用:复用js, 简化js的编写, 提高js运行效率

组件

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  2. 为什么要用组件: 一个界面的功能更复杂
  3. 作用:复用编码, 简化项目编码, 提高运行效率

模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

//函数式组件 
function Top(){ //首字母一定要大写
    //返回虚拟 DOM 对象
    return <header></header>
}
​
//内容区组件
function Content(){
    return <main></main>
}
​
//底部区组件
function Bottom(){
    return <footer></footer>
}
​
// <Top></Top>  React 会自动寻找与 Top 同名的『函数』, 执行函数返回『虚拟 DOM 对象』, 最终渲染
function App(){
    return <div>
        <Top></Top>
        <Content />
        <Bottom />
    </div>;
}
​
ReactDOM.render(<App />, document.querySelector("#root"));
//类式组件
class Top extends React.Component{
    //这里 render 的名字是固定的
    render(){
        return <header></header>;
    }
}
​
//内容区组件
class Main extends React.Component{
    //这里 render 的名字是固定的
    render(){
        return <main></main>;
    }
}
​
//尾部区组件
class Footer extends React.Component{
    render(){
        return <footer></footer>;
    }
}
​
//创建虚拟 DOM 对象
// <Top></Top>  寻找与 Top 同名的类, 实例化对象, 并调用 render 方法得到虚拟 DOM 对象, 最终渲染
class App extends React.Component{
    render(){
        return <div>
                    <Top></Top>
                    <Main />
                    <Footer/>
                </div>
    }
}
​
ReactDOM.render(<App />, document.querySelector("#root"));

组件三大核心属性1: state

  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)

  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

  3. 状态. state 是类式组件实例对象中的『一个属性』. 初始值是对象, 里面可以『存储属性数据』.

    特点: 当对 state 对象中的属性进行设置的时候, 会自动重新调用 render 方法, 渲染组件

强烈注意

  1. 组件中render方法中的this为组件实例对象
  2. 组件自定义的方法中this为undefined,如何解决?
  3. 强制绑定this: 通过函数对象的bind()
  4. 箭头函数
  5. 状态数据,不能直接修改或更新

组件三大核心属性2: props

  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所有属性都保存在props中

props作用

props 是 properties 的缩写, 单词本意『属性』. 可以接收组件外传入组件的数据, 实现『组件的解耦与复用』

  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意: 组件内部不要修改props数据

组件三大核心属性 refs与事件处理

理解

ref 单词『reference』的缩写, 『引用』. ref 是类式组件实例对象中的一个属性, 可以帮忙快速获取『元素对象 (DOM对象)』

组件内的标签可以定义ref属性来标识自己

ref 使用方式选择

  1. 字符串
  2. 回调函数 √
  3. createRef √

事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
  2. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
  3. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  4. 通过event.target得到发生事件的DOM元素对象

React 中事件回调的 this

默认是指向 『undefined』

谈谈你对 xxx 的理解 (3w1h)

  1. 是什么
  2. 作用
  3. 特点
  4. 怎么用

谷歌应用商店网址

chrome.google.com/webstore

vscode

ctrl + d 选中相同的文本内容

事件

  1. 鼠标事件 click dblclick mouseover mouseenter mousemove mouseout mouseleave mousedown mouseup
  2. 键盘事件 keydown keyup keypress
  3. 滚轮事件 mousewheel
  4. 表单事件 focus blur submit (e.preventDefault)
  5. 文档事件 load beforeunload

绑定方式

  1. ​​

    <div onclick="alert(123)"></div>
    
  2. div.onclick = function(){}

  3. div.addEventListener('click', function(){});

  • unicode 是字符集
  • utf-8 是 unicode 的编码方式

e.target

是事件触发时, 所操作的『元素』

鼠标事件小结

  1. 事件源 e.target
  2. 阻止默认行为 e.preventDefault(放在第一行)
  3. 阻止冒泡(阻止捕获) e.stopPropagation
  4. 获取鼠标的位置 e.clientX e.pageX e.pageY

react 事件补充

  1. 合成事件
  2. 效率高 事件绑定在了 document 身上, 事件委派.
  3. 兼容性好

高阶函数

函数.

高阶函数: 是一种函数, 如果返回了一个函数, 或者接受函数类型的参数, 就被称之为是『高阶函数』

情况:

  • Promise
  • then
  • 数组方法 map forEach some every filter find
  • 定时器 setTimeout setInterval
  • bind

阻止默认行为,阻止ctrl+c的功能

//阻止右键菜单事件
window.oncontextmenu = function(e){
    e.preventDefault();
}
//阻止ctrl+c的功能(不能复制)
window.onkeydown = function(e){
    if(e.keyCode === 67 && e.ctrlKey) {
        e.preventDefault();
    }
}
//受控组件,获取b
class App extends React.Component{
//1. 声明状态
state = {
    user: '',
    pass: '',
    code: '',
    phone: ''
}
​
render(){
    return <div>
            <h2>登录</h2>
            <form action="">
                {/**2. 为表单元素 设置 value 与 onChange **/}
                用户名: <input type="text"  value={this.state.user}  onChange={this.saveData('user')}  /><br />    
                密码: <input type="password" value={this.state.pass} onChange={this.saveData('pass')}  /><br />    
                验证码: <input type="text" value={this.state.code} onChange={this.saveData('code')}  /><br />    
                手机号: <input type="text" value={this.state.phone} onChange={this.saveData('phone')}  /><br />    
                <input type="submit" value="登录" onClick={this.login} />
            </form>
        </div>
}
​
//方法
saveData = (type) => {
    return (e) => {
        this.setState({
            [type]: e.target.value
        });
    }
}
​
login = (e) => {
    //获取表单项的值
    e.preventDefault();
​
    console.log(this.state.user);
    console.log(this.state.pass);
}
}
​
ReactDOM.render(<App />, document.querySelector("#root"));

生命周期

理解

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

经典面试题

/**
    经典面试题:
        1). react/vue中的key有什么作用?(key的内部原理是什么?)
        2). 为什么遍历列表时,key最好不要用index?
        3). 请你简单的聊聊DOM的Diffing算法?
            1. 虚拟DOM中key的作用:
                1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时,key起着极其重要的作用。
​
                2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
​
                    a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
                        (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
                        (2).若虚拟DOM中内容变了, 则更新之前的真实 DOM
​
                    b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
                        根据数据创建新的真实DOM,随后渲染到到页面
​
            2. 用index作为key可能会引发的问题:
                1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
                        会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
​
                2. 如果结构中还包含输入类的DOM:
                        会产生错误DOM更新 ==> 界面有问题。
​
                3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
                        仅用于渲染列表用于展示,使用index作为key是没有问题的。
​
            3. 开发中如何选择key?:
                1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
                2.如果确定只是简单的展示数据,用index也是可以的。
​
*/

React脚手架创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

消息订阅发布机制

  1. 工具库: PubSubJS

  2. 下载: npm install pubsub-js --save

  3. 使用:

    import PubSub from 'pubsub-js' //引入

    PubSub.subscribe('name', function(data){ }); //订阅

    PubSub.publish('name', data) //发布消息

React路由

路由分类

后端路由:

理解: value是function, 用来处理客户端提交的请求。

注册路由: router.get(path, function(req, res))

工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由:

浏览器端路由,value是component,用于展示页面内容。

注册路由:

工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

React中路由的使用

//安装react-router-dom5版本
npm i react-router-dom@5

react-router-dom相关API

内置组件

<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
//具体使用
import { BrowserRouter, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
​
export default class App extends React.Component {
    render() {
        return <BrowserRouter><div>
            <div className="row">
                <div className="col-xs-offset-2 col-xs-8">
                    <div className="page-header">
                        <h2>React Router Demo</h2>
                    </div>
                </div>
            </div>
            <div className="row">
                <div className="col-xs-2 col-xs-offset-2">
                    <div className="list-group">
                        <NavLink className="list-group-item" to="/about">About</NavLink>
                        <NavLink className="list-group-item" to="/home">Home</NavLink>
                    </div>
                </div>
                <div className="col-xs-6">
                <Switch>
                    <Route path="/home" component={Home} />
                    <Route path="/about" component={About} />
                    <Redirect to="/home" />
                </Switch>
                </div>
            </div>
        </div>
        </BrowserRouter>;
    }
}

withRouter函数可以把一个非路由组件转换成路由组件

import { withRouter } from 'react-router-dom';
import Redux from './components/Redux/Redux';
class App extends React.Component {
    render() {
        return <div className='container'>
            <Redux />
        </div> ;
    }
}
​
export default withRouter(App);

切换版本

  • git reset --hard 『id 版本号』
  • 如果忘记了,或者找不到版本号了, 可以使用 『git reflog』查看版本操作的历史

路由组件与一般组件的区别

  1. 使用方法不同

    • 路由组件:
    • 一般组件:
  2. 存放位置不同

    • 路由组件: src/pages 文件夹 pages 页面
    • 一般组件: src/components 文件夹
  3. props 属性值不同

    • 路由组件: {history, match, location}
    • 一般组件: {a: '100', b: '200'}

params传参

1. 路由设置  `<Route path="/video" component={VideoDetail} />`
2.列表组件 <Link to={ "/video/"+item.id}> </Link>
3. VideoDetail组件  let id = this.props.location.pathname.split('/').pop();

params传参的简便写法

  1. 路由设置 <Route path="/video/:id" component={VideoDetail} />
  2. VideoDetail组件 let id = this.props.match.params.id;

query传参的流程

  1. 列表组件 <Link to={"/video?id=" + item.id}>

  2. 详情组件

    let search = this.props.location.search.slice(1); // ?id=1  => {'?id' : 1}
    //在文件最上方需要引入qs模块
    let res = qs.parse(search);// {id: 1}
    let id = res.id;
    

state 传参

  1. 列表组件

    <Link to={{
       pathname: '/video', //设置 URL 的路径
       state: {
             id: item.id
       } //设置要传递的参数
    }}>
    
  2. video组件

    let id = this.props.location.state.id;
    

关于路由中的 URL

  1. 通过路由改变页面的 URL 不会发送请求
  2. 组件中发送 AJAX 请求的 URL 一定会发送请求

编程式路由导航

通过调用 JS 的方法, 实现路由的切换

//切换路由
check = (id) => {
    return () => {
        //调用方法 push 切换路由
        //params
        this.props.history.push('/video/' + id);
        //query
        // this.props.history.push('/video?id=' + id);
        //state
        // this.props.history.push({
        //     pathname: '/video',
        //     state: {
        //         id: id
        //     }
        // })
​
        //replace 方法切换路由  替换   用法与 push 完全相同
        // this.props.history.replace({
        //     pathname: '/video',
        //     state: {
        //         id: id
        //     }
        // })
​
    }
}
//路由的前进与后退
前进:this.props.history.go(1);
后退:this.props.history.go(-1);

两种路由器组件的区别

  1. URL 的形式不同

    • BrowserRouter /video/2 更友好
    • HashRouter #/video/2 弱
  2. 兼容性不同

    • BrowserRouter 相对较弱 IE9
    • HashRouter 相对较好 IE8
  3. 部署复杂性

    • BrowserRouter 较为复杂 URL 重写
    • HashRouter 较为简单

redux最终版

  1. 在src目录下创建redux文件夹

目录.png

  • actions文件夹:封装创建 action 对象的函数的js文件
  • reducers文件夹:reducer 函数 『加工厂』的js文件
  • constants.js文件:声明一些常量
  • store.js文件:主文件

store.js中代码如下:

npm i redux   
npm i redux-devtools-extension //安装redux开发工具
npm i redux-thunk   //安装redux-chunk使store.dispatch()可以接收一个函数作为参数import { composeWithDevTools } from 'redux-devtools-extension';
​
导入『创建状态仓库』的函数 createStore create 创建  store 仓库
import {createStore, applyMiddleware, combineReducers} from 'redux';
​
import thunk from 'redux-thunk';
import CollReducer from './reducers/CollReducer';
import CommentReducer from './reducers/CommentReducer';
​
//合并 reducer 函数
let reducer = combineReducers({
    coll: CollReducer,
    comment: CommentReducer
});
​
​
//4. 调用函数, 创建状态仓库  action 是一个对象, 两个属性: type 操作类型  data 操作使用的数据
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
​
//异步修改状态 
// store.dispatch((dispatch) => {
//     setTimeout(() => {
//         dispatch({type: 'COLL_ADD', data: 5})
//     }, 2000)
// })//6. 暴露 store 对象
export default store;

reducers文件夹下的CollReducer.js

//导入常量
import {COLL_ADD, COLL_MINUS} from '../constants';
//声明一个函数
const CollReducer = (state=100, action) => {  // reducer 函数  『加工厂』
    //根据操作的类型, 对 state 状态进行操作
    switch(action.type){
        //加法
        case COLL_ADD:
            //返回值, 将作为新的状态值.   更新状态
            return state + action.data;
        //减法 
        case COLL_MINUS:
            return state - action.data;
        //其他情况
        default: 
            //这里一定要返回 state. 初始化的时候, 会执行该函数, 得到状态的初始值
            return state;
    }
}
​
export default CollReducer;

reducers文件夹下的CommentReducer.js

import {COMMENT_ADD, COMMENT_MINUS} from '../constants'
//声明一个函数
const CommentReducer = (state=10, action) => {  // reducer 函数  『加工厂』
    //根据操作的类型, 对 state 状态进行操作
    switch(action.type){
        //加法
        case COMMENT_ADD:
            //返回值, 将作为新的状态值.   更新状态
            return state + action.data;
        //减法 
        case COMMENT_MINUS:
            return state - action.data;
        //其他情况
        default: 
            //这里一定要返回 state. 初始化的时候, 会执行该函数, 得到状态的初始值
            return state;
    }
}
​
export default CommentReducer;

actions文件夹下的CollAction.js

import {COLL_ADD, COLL_MINUS} from '../constants'
//5-3 封装创建 action 对象的函数
export function addAction(data){
    return {
        type: COLL_ADD,
        data
    }
}
​
export function minusAction(data){
    return {
        type: COLL_MINUS,
        data
    }
}
​
//5-4 异步修改状态
export function asyncAddAction(data){
    return dispatch => {
        //定时器
        setTimeout(() => {
            dispatch(addAction(data));
        }, 1000)
    }
}

actions文件夹下的CommentAction.js

//封装暴露函数, 作用: 返回 action 对象
import {COMMENT_ADD, COMMENT_MINUS} from '../constants';
​
export function jiaAction(data){
    return {
        type: COMMENT_ADD,
        data: data
    }
}
​
export function jianAction(data){
    return {
        type: COMMENT_MINUS,
        data: data
    }
}
​
export function asyncJiaAction(data){
    return dispatch => {
        setTimeout(() => {
            dispatch(jiaAction(data));
        }, 1000)
    }
}
​
​
// store.dispatch({type: 'jia', data: 1});
// store.dispatch(jiaAction(1));

constants.js文件:

// constant 固定的 常量的
​
//收藏数
export const COLL_ADD = 'COLL_ADD';
export const COLL_MINUS = 'COLL_MINUS';
​
//评论数
export const COMMENT_ADD = 'COMMENT_ADD';
export const COMMENT_MINUS = 'COMMENT_MINUS';

在组件中的使用:

import React, { Component } from "react";
import store from "../../redux/store";
import { addAction, minusAction, asyncAddAction } from '../../redux/actions/CollAction';
import { jiaAction, jianAction, asyncJiaAction } from "../../redux/actions/CommentAction";
export default class Redux extends Component {
    render() {
        return (
            <div>
                <br />
                <br />
                <br />
                <br />
                <h2>Redux 状态操作</h2> 
                <hr />
                <h4>收藏数 {store.getState().coll}</h4>
                <button className="btn btn-danger btn-sm" onClick={this.minus}>
                    减少
                </button>     
                &nbsp;
                <button className="btn btn-primary btn-sm" onClick={this.add}>
                    新增 
                </button>   
                &nbsp;
                <button className="btn btn-info btn-sm" onClick={this.asyncAdd}>
                    1s 后新增 2 
                </button>
​
                <h4>评论数 {store.getState().comment}</h4>
                <button onClick={() => {
                    store.dispatch(jianAction(1));
                }}>减少</button>
                <button onClick={() => {
                    store.dispatch(jiaAction(1));
                }}>增加</button>
                <button onClick={() => {
                    store.dispatch(asyncJiaAction(1));
                }}>1s 后增加评论数</button>
            </div>
        );
    }
​
    add = () => {
        //新增状态值
        store.dispatch(addAction(1));
    };
​
    minus = () => {
        //减少状态值
        store.dispatch(minusAction(1));     
    }
​
    //异步修改状态
    asyncAdd = () => {
        store.dispatch(asyncAddAction(10));
    }
​
    componentDidMount(){
        console.log(store.getState());
    }
}
​

React函数式组件

import React from 'react'
import axios from 'axios';
export default function Func(props) {
    //props属性
    console.log(props);
​
    //创建一个状态,名为:night,值为:true
    //setNight 函数, 用来修改状态的
    let [night, setNight] = React.useState(true);
    let [duanzi, setDuanzi] = React.useState([]);
​
    //点击事件的回调
    let handleClick = () => {
        //修改状态
        setNight(!night);
    }
​
    // ref 属性
    const h4 = React.useRef();
    //事件回调
    let setHtml = () => {
        // console.log(h4);
        h4.current.innerHTML = '哈哈'
    }
​
    //获取段子列表的回调
    let getDuanzi = async () => {
        let { data } = await axios.get('http://api.xiaohigh.com/duanzi?_limit=10');
        setDuanzi(data);
    }
​
    //模拟生命周期钩子  相当于是 componentDidMount 与 componentDidUpdate 的合体
    React.useEffect(() => {
        //钩子输出 
        console.log('我执行啦');
        //声明异步函数
        async function fn() {
            let result = await axios.get('http://api.xiaohigh.com/duanzi?_limit=3');
            setDuanzi(result.data);
        }
        //调用函数
        fn();
​
        //模拟 componentWillUnmount
        //return 后面的函数类似于componentWillUnmount
        return () => {
            console.log('我卸载了!!');
        }
    }, [night]) // [] 设置的是, 哪些状态修改之后, 会再次执行回调
​
​
    return (
        <div style={{ padding: '50px', height: '2000px' }}>
            <h2>状态 - state</h2>
            <h4>至尊宝, 我是 {night ? '青霞' : '紫霞'}</h4>
            <button onClick={handleClick}>切换白天与晚上</button>
            <br /><br /><br />
​
            <h2>属性 - props</h2>
            <h4>{props.msg}</h4>
            <br /><br /><br />
​
            <h2>引用 - ref</h2>
            <h4 ref={h4}></h4>
            <button onClick={setHtml}>点击修改 H4 的文本内容</button>
            <br /><br /><br />
​
            <h3>发送AJAX请求</h3>
            <button onClick={getDuanzi}>获取段子</button>
            <ul>
                {duanzi.map(item => {
                    return <li key={item.id}>{item.name}</li>
                })}
            </ul>
            <br /><br /><br />
​
​
        </div>
    )
}
​

\