react0.1

119 阅读29分钟

React全家桶(技术栈)

一、React介绍

1.1- 什么是React

官方:用于构建用户界面的 JavaScript 库 。

所以React并不是一个框架,而是一个用于前端数据渲染的库。

而人们常指的React框架指的是:react+react-router+react-redux的组合。

React框架是渐进式的。

React 起源于 Facebook 的内部项目,最初来架设 Instagram ,后于 2013 年 5 月开源。

1.2- React的特点

高性能:虚拟DOM(Virtual DOM)配合 diff 算法,最大限度地减少与 DOM 的交互。

声明式:React采用声明式编码UI,可以轻松实现交互式UI,让代码更加可行且方便调试。

组件化:通过 React 构建组件,使得代码更加容易得到复用,能够高效率的应用在大项目的开发中。

单向响应的数据流:React 中的数据是单向自顶向下传递的,父组件数据的更新会自动传递到子组件,但子组件的数据更新不会影响到父组件,也不能在子组件修改父组件传入的数据

JSX扩展:JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但官网建议使用它。

灵活:React可以与已知的库或框架很好的进行配合。

1.3- 国内reactjs开发的项目

腾讯云:cloud.tencent.com/

京东会员中心:home.jd.com/

阿里云: www.aliyun.com/

七牛云控制中心 portal.qiniu.com/create

支付宝商家中心:mrchportalweb.alipay.com/user/home

……

1.4- React官网

英文官网: https://reactjs.org/

中文官网: zh-hans.reactjs.org/

二、React基本操作

2.1- 页面中使用react

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--  react核心文件  -->
    <script src="./lib/react.development.js"></script>
    <!--  用于DOM操作,构建界面,react-dom依赖于react,所以一定要先引入react再引入react-dom  -->
    <script src="./lib/react-dom.development.js"></script>
</head>
<body>
    <!-- react项目提根元素,该元素提供了react展现的舞台-->
    <div id="root">
​
    </div>
</body>
<script>
    // 通过ReactDOM.createRoot可以创建React容器
    // ReactDOM由react-dom.development.js来提供
    // 将id为root的元素作为React容器
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    // render指定呈现的内容。(指定挂载的数据)
    root.render("你现在过的还好吗?");
</script>
</html>

2.2- 注意事项

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
</head>
<body>
    <div id="root"></div>
    <div class="app"></div>
</body>
<script>
    {
        // 1- 挂载的位置(react容器)同一个页面可以指定多个
        // 将id为root的元素作为挂载的容器(react容器)
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render("我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪");
​
        // 将class为app的元素作为挂载的容器(react容器)
        // const app = ReactDOM.createRoot(document.querySelector(".app"));
        // app.render("我确定你就是那只披着羊皮的狼");
    }
    {
        // 2- render可以调用多次,最后一次呈现的内容,会将之前的覆盖掉
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render("我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪");
        // root.render("365只烛光,映在我脸上");
    }
    {
        // 3- 挂载的位置,不要被指定多次
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render("我现在很好!");
        // const root2 = ReactDOM.createRoot(document.querySelector("#root"));
        // root2.render("你在或不在,我都不等你,你来或不来,也都无所谓");
    }
    {
        // 4- 不要将body或html作为react容器
        // const root = ReactDOM.createRoot(document.body);
        // root.render("不要将body作为容器,因为body当中的子项,经常被第三方的脚本或浏览器插件使用");
​
        // const root = ReactDOM.createRoot(document.documentElement);
        // root.render("不要将html作为容器");
    }
    {
        // 5- render呈现的内容,如果是字符串,字符串使用html标签,不会被浏览器识别
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render("<a>我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪</a>");
    }
    {
        // 6- 可以使用连缀写法
        ReactDOM.createRoot(document.querySelector("#root")).render("<a>我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪</a>");
    }
</script>
</html>

2.3- 更新与卸载

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
</head>
<body>
    <button>点我</button>
    <button>卸载</button>
    <div id="root"></div>
</body>
<script>
    // 1- 生成react容器
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    const btns = document.querySelectorAll("button");
    let num = 100;
    // 2- 为react容器挂载内容
    root.render(num);
    btns[0].onclick = function(){
        root.render(++num);
    }
    btns[1].onclick = function(){
        // 3- 卸载react容器
        root.unmount();
    }
​
</script>
</html>

三- 虚拟DOM

3.1- 虚拟DOM的理解

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body></body>
<script>
    /*
    1- 浏览器渲染的流程是什么?
    答:当浏览器发送一个请求,会得到对应的响应。浏览器会通过HTML解析器去解析HTML会构建DOM树,会通过CSS解析器去解析CSS生成CSS规则树。
    如果页面中拥有一些JS逻辑,那么往往会通过JS将CSS,HTML进行修改的操作。往往造成重排重绘。
    2- 什么是重排重绘?
    答:当元素的几何属性(大小,位置相关属性)发生变化后,浏览器会将几何属性进行重新的计算,会影响到其它的元素。
    那么受影响的元素对应的几何属性也会发生变化(几何属性会重新计算)。
    几何属性进行的计算称为重排。
    重排之后,需要将结果进行渲染,渲染的过程称为重绘。
    结论:重排之后肯定会进行重绘,重绘不会进行重排。
    this.style.width = "100px";
    this.style.height="200px";
    3- 传统DOM有什么缺陷?
    答:当页面比较复杂时,JS如果频繁更改几何属性,那么会产生很多次重排重绘,从而性能会降低,浪费执行时间。
    4- React高效的原因是什么?
    答:React不会直接操作真实DOM,降低重排重绘。React操作的是虚拟DOM.
    5- 虚拟DOM是什么?有什么特点?
        答: 1- 虚拟DOM是JS的一个对象,该对象下的属性对应着真实DOM的属性。
            2- 虚拟DOM对象当中的属性,是根据实际操作决定的,虚拟DOM是轻量级的,而真实DOM是重量级。
            3- 最终虚拟DOM会转为真实的DOM进行渲染。你可以将虚拟DOM理解为真实DOM的一个副本或缓存。
            4- 虚拟DOM也被称为虚拟DOM元素,在React当中称为React元素。
            5- 如何在React当中创建并使用虚拟DOM
                5-1 React.createElement()(了解)
                5-2 jsx (重点),jsx的本质其实是React.createElement()的语法糖。
    * */
</script>
</html>

3.2- createElement创建虚拟DOM(了解)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
    <style>
        .two{
            color:red;
        }
    </style>
</head>
<body>
    <div id="root">
​
    </div>
</body>
<script>
    {
        // 创建真实DOM
        // document.createElement()
        // 创建虚拟DOM(英文:virtual dom)
        // React.createElement()
    }
    // 生成React容器
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    {
        // 1-字符串当中的HTML标签不会被浏览器识别
        root.render("<a>字符串</a>")
    }
    {
        // 2- 可以通过React.createElement创建虚拟DOM,然后通过root.render接收即可
        // createElement一般使用三个参数
        // 1- 标签的名字(type)  必须填写
        // 2- 指定属性 (props)  可以省略
        // 3- 指定包裹的内容。(props.children) 可以省略
        // 以下虚拟DOM对应着 <div id="one" class="two">最近有没有什么好看电影啊</div>
        // const div = React.createElement("div",{
        //     id:"one",
        //     className:"two"
        // },"最近有没有什么好看电影啊");
        // console.log(div);
        // root.render(div);// 如果render接收的是一个属性DOM,会将虚拟DOM转为真实DOM进行渲染。
    }
    {
        // 3-第二个参数不可以直接跳过,如果没有属性可以设置,那么可以将第二个参数设置为空对象或null
        // const span = React.createElement("span",{},"span标签");
        // const span = React.createElement("span",null,"span标签");
        // root.render(span);
    }
    {
        // 4- 如果结构比较复杂,使用React.createElement不太适宜。
        // 4-1 如果指定的内容是字符串,字符串中又使用了标签,那么虚拟DOM转为真实DOM,不会将span作为浏览器识别标签。
        // const div = React.createElement("div",{className:"two"},"<span>span标签</span>");
        // root.render(div);
​
        // 4-2 可以先创建span,再创建div,将span(虚拟DOM)作为div的第三个参数
        const span = React.createElement("span",null,"span标签");
        const p = React.createElement("p",null,"p标签");
        // 从第三个参数开始为包裹的元素
        // const div = React.createElement("div",{className:"two"},span,span,span,span,span,p);
        // 也可以将包裹的元素放置到数组中
        const div = React.createElement("div",{className:"two"},[span,span,span,span,span,p]);
        root.render(div);
    }
    {
        // 总结:React.createElement可以创建虚拟DOM,可以将创建好的虚拟DOM作为参数传递给render,render会将虚拟DOM转为真实DOM
        //      但是React.createElement不太适合复杂的界面设计。
    }
​
</script>
</html>

四- JSX(重点)

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模板语言,但它具有 JavaScript 的全部功能。

JSX 可以生成 React “元素”。

4-1 JSX的基本用法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
    <!--  可以支持JSX语法  -->
    <script src="./lib/babel.min.js"></script>
    <style>
        .two{
            color:red;
        }
    </style>
</head>
<body>
    <div id="root"></div>
</body>
<!--<script src="./lib/my.js" type="text/babel"></script>-->
<script src="./lib/my.jsx" type="text/babel"></script>
<script type="text/babel">
    // 创建React容器
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    {
        // jsx全称:javascript xml,
        // jsx是JAVASCRIPT的一个扩展语法,扩展语法浏览器不支持,借助于babel.js
        // 1- 引入babel.min.js
        // 2- 在script标签中增加type=text/babel
        // <div></div> 相当于React.createElement("div");
        // root.render(<div></div>);
    }
    {
        // div中嵌套span,span内写入内容
        // 相当于React.createElement("div",{id:"one",className:"two"},"今天学习JSX")
        // root.render(<div id="one" className="two">今天学习JSX</div>)
        // root.render(<div><span>span1</span><span>span2</span><p>1</p></div>)
        // root.render(
        //     <div>
        //         <span>span1</span>
        //         <span>span2</span>
        //         <p>1</p>
        //     </div>
        // )
    }
    {
        // 强烈建议:如果写入的JSX有嵌套,建议使用()将其包裹
        // root.render((
        //     <div>
        //         <span>1</span>
        //         <span>2</span>
        //         <span>3</span>
        //         <span>4</span>
        //         <p>2</p>
        //     </div>
        // ))
    }
    {
        // 元素变量:值是虚拟DOM元素(React元素)的变量
        // 元素常量:值是虚拟DOM元素(React元素)的常量
        // const vDom = (
        //     <div>
        //         <span>1</span>
        //         <span>2</span>
        //         <span>3</span>
        //         <span>4</span>
        //     </div>
        // )
        // root.render(vDom);
    }
    {
        // 将元素常量放置到外部定义。 lib->my.js
        // 需要script引入,type=text/babel
        // root.render(vDom);
    }
    {
        // 如果将元素常量放置到外部,强烈建议:将文件的名字以jsx结尾。
        // 在开发过程中,如果你的文件中使用了JSX,一般以.jsx作为扩展名
        // root.render(vDom);
    }
    {
        // 如果只是单纯的使用一个标签,建议:不需要使用括号,直接闭合即可
        // root.render(<div/>)
    }
    {
        // 注意1:在使用JSX时,标签必须要闭合
        // root.render((
        //     <div>
        //         <input type="text"/>
        //         <span>1</span>
        //     </div>
        // ))
    }
    {
        // 注意2:在使用JSX时,标签的名字首字母不允许大写
        // 首字母大写会认为你使用的是组件,组件是需要定义的。
        // root.render((
        //     <div>
        //         <Span></Span>
        //     </div>
        // ))
    }
    {
        // 注意3:在使用JSX时,不允许出现HTML没有内置标签(浏览器不支持的标签,不允许出现)
        // root.render((
        //     <div>
        //         <user></user>
        //     </div>
        // ))
    }
    {
        // 注意4:传入的JSX有且只能是一个根元素
        // root.render((
        //     <div></div>
        //     <div></div>
        // ))
    }
    {
        // 注意5:如果有显示多个兄弟元素,可以在外围增加一个div
        // root.render((
        //     <div>
        //         <div>1</div>
        //         <div>2</div>
        //     </div>
        // ))
    }
    {
        // 注意6:可以使用React提供的包裹组件,来解决一个根元素的问题
        // React.Fragment只是起到了一个包裹的作用,不会转为真实dom
        // 1
        // root.render((
        //     <React.Fragment>
        //         <div>1</div>
        //         <div>2</div>
        //     </React.Fragment>
        // ))
​
        // 2
        // const {Fragment} = React;
        // root.render((
        //     <Fragment>
        //         <div>1</div>
        //         <div>2</div>
        //     </Fragment>
        // ))
​
        // 3- <></>也是一个包裹标签,是React.Fragment的语法糖。
        root.render((
            <>
                <div>1</div>
                <div>2</div>
            </>
        ))
    }
​
</script>
</html>

4-2 插值表达式

  /*
    * jsx规则:
    *      1- 对JSX进行解析时,如果遇到<开头,那么<的内容首字母小写,会被作为HTML标签处理,如果<的内容首字母大写会被作为组件处理。
    *      2- 对JSX进行解析时,如果遇到{开头,那么会被作为JS表达式去处理,并输出其表达式的结果。
    * {}写入的内容只支持表达式,不支持JS语法。
    * js语句:
    *   if 条件语句
    *   for 循环语句
    *   switch 选择语句
    * js表达式:一个表达式会对应一个结果,表达式其实是一种特殊的JS语句
    *   三元表达式
    *   变量
    *   常量
    *   fn()
    * */
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    let n = 3;
    let str = "字符串";
    let arr = [1,2,3,4];// 通过表达式输出为  1234
    // 不允许将对象作为插值表达式
    let obj = {
        a:1,
        b:2
    }
    // 注意:插值表达式中如果写入的是true,false,undefined,null不会渲染任何内容
    root.render((
        <div>
            <p>1</p>
            <p>{2}</p>
            <p>{n}</p>
            <p>{str}</p>
            <p>{"str"}</p>
            <p>{arr}</p>
            {/*<p>{obj}</p>*/}
            <p>{undefined}</p>
            <p>{true}</p>
            <p>{false}</p>
            <p>{null}</p>
        </div>
    ))

4-3- 条件渲染

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    {
        // 三元表达式来处理
        // let sex = 1;
        // let bol = false;
        // root.render((
        //     <div>{sex===1?"男":"女"}|{bol?"真":"假"}</div>
        // ))
    }
    {
        // &&与运算符:该运算符左侧为true时,返回右侧的内容,如果为false,返回左侧内容。
        // let bol = false;
        // root.render((
        //     <div>
        //         {
        //             bol && "今天天气不错"
        //         }
        //     </div>
        // ))
    }
    {
        // ||或运算符:该运算符与&&运算符正好相反。左侧为false才会渲染右侧的值。
        // let bol = true;
        // root.render((
        //     <div>
        //         {
        //             bol || "周杰伦"
        //         }
        //     </div>
        // ))
    }
    {
        function fn(age){
            if(age>60) return "老年";
            if(age>30) return "中间";
            if(age>18) return "青年";
            if(age>12) return "少年";
            if(age>6) return "儿童";
            return "婴儿"
        }
         root.render((
             <div>
                 <p>{fn(10)}</p>
                 <p>{fn(40)}</p>
                 <p>{fn(80)}</p>
                 <p>{fn(1)}</p>
             </div>
         ))
    }

4-4 属性渲染

    const root = ReactDOM.createRoot(document.querySelector("#root"));
    let imgSrc = "https://t7.baidu.com/it/u=3332251293,4211134448&fm=193&f=GIF";
    let imgSrc2 = "https://t7.baidu.com/it/u=3505622605,280817994&fm=193&f=GIF";
    const width = 100;
    const bol = false;
    root.render((
        <div>
            <img width="200" src="https://t7.baidu.com/it/u=1951548898,3927145&fm=193&f=GIF" alt=""/>
            <img width={width} src={imgSrc} alt=""/>
            <img width={width} src={bol ? imgSrc : imgSrc2} alt=""/>
        </div>
    ))

4-5- className

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    const bg = "bg";
    const one = "cl";
    const arr = ["bg","cl"];// 数组作为属性进行展示时是有逗号的,直接渲染是不带逗号
    root.render((
        <div>
            <p className="bg">1</p>
            <p className="cl">2</p>
            <p className="bg cl">3</p>
            <p className={"bg"}>4</p>
            <p className={"cl"}>5</p>
            <p className={"bg cl"}>6</p>
            <p className={bg}>7</p>
            <p className={one}>8</p>
            <p>{arr}</p>
            <p className={arr}>10</p>
        </div>
    ))

4-6- style

 // style的属性值必须要设置为对象
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    const my ={
        width:"200px",
        height:"300px",
        background:"green"
    }
    root.render((
        <div>
            {/*以下是一个错误示例*/}
            {
                // <div style="width:100px;height:100px;background:red;"></div>
            }
            <div style={{
                width:"100px",
                height:"200px",
                background:"red"
            }}></div>
​
            <div style={my}></div>
        </div>
    ))

4-7- 列表渲染

  const root = ReactDOM.createRoot(document.querySelector("#root"));
    {
        // 1- 数组可以直接展开
        // const arr = [1,2,3,4];
        // root.render((
        //     <div>{arr}</div>
        // ))
    }
    {
        // 2- 数组内可以将JSX作为元素
        // const arr = [<h3>1</h3>,<h3>2</h3>,<h3>3</h3>];
        // root.render((
        //     <div>{arr}</div>
        // ))
    }
    {
        // 3- 通过ajax得到一些数据
        // const newsList = [
        //     {
        //         newsTitle:"第一篇新闻"
        //     },{
        //         newsTitle:"第二篇新闻"
        //     }
        // ]
        // 3-1- forEach一
        // const arr = [];
        // newsList.forEach(item=>{
        //     arr.push(<p>{item.newsTitle}</p>)
        // })
        // root.render((
        //     <div>{arr}</div>
        // ))
​
        // 3-2- forEach二
        // const arr = [];
        // newsList.forEach(item=>arr.push(<p>{item.newsTitle}</p>))
        // root.render((
        //     <div>{arr}</div>
        // ))
​
        // 3-2- forEach三
        // const arr = [];
        // newsList.forEach(item=>arr.push((
        //     <p>
        //         <a>{item.newsTitle}</a>
        //     </p>
        // )))
        // root.render((
        //     <div>{arr}</div>
        // ))
    }
    {
        // 4- 通过map渲染列表
        // const newsList = [
        //     {
        //         newsTitle:"第一篇新闻"
        //     },{
        //         newsTitle:"第二篇新闻"
        //     }
        // ];
​
        // // 4-1- 可以先定义再输出,{}
        // const arr = newsList.map(item=>{
        //     const {newsTitle} =item;
        //     return (
        //         <p>{newsTitle}</p>
        //     )
        // })
        // root.render((
        //     <div>{arr}</div>
        // ))
​
        // 4-2- 可以先定义再输出,()优点:简捷,缺点:无法写逻辑
        // const arr = newsList.map(item=>(
        //     <p>{item.newsTitle}</p>
        // ))
        // root.render((
        //     <div>{arr}</div>
        // ))
​
        // 4-3- 可以先定义再输出,返回的内容有且只能是一个根元素{}
        // const arr = newsList.map((item,index)=>{
        //     return (
        //         <>
        //             <div>{index}</div>
        //             <div>{item.newsTitle}</div>
        //         </>
        //     )
        // })
        // root.render(<div>{arr}</div>)
​
        // 4-4- 可以先定义再输出,返回的内容有且只能是一个根元素()
        // const arr = newsList.map((item,index)=>(
        //     <>
        //         <div>{index}</div>
        //         <div>{item.newsTitle}</div>
        //     </>
        // ))
        // root.render(<div>{arr}</div>)
​
    }
    {
        const newsList = [
            {
                newsTitle:"第一篇新闻"
            },{
                newsTitle:"第二篇新闻"
            }
        ];
        // 5- 可以直接通过map输出
        root.render((
            <div>
                {
                    newsList.map(item=>(
                        <p>{item.newsTitle}</p>
                    ))
                }
            </div>
        ))
    }

4-8 KEY

    const root = ReactDOM.createRoot(document.querySelector("#root"));
    {
        // 在列表渲染时需要使用key,key是独一无二,不允许重复
        const arr = [1, 2, 3, 4,4];
        root.render((
            <div>
                {
                    arr.map((item,index)=>(
                        <div key={index}>{item}</div>
                    ))
                }
            </div>
        ))
    }

4-9- 注释

 // 1- 在非属性中,注释要写到花括号内
    // 2- 在属性中,注释不需要写到花括号内
    // 生成react元素(虚拟DOM元素)(jsx): javascript xml
    // class===>className
    // style===>{}fontSize
    const vDOM = (
        <div>
            {
                // 这是单行注释
            }
            <p>1</p>
            {
                /*
                * 多
                * 行
                * 注
                * 释
                * */
            }
            <p>2</p>
            {/*注释*/}
            <p>3</p>
            <div
                // 注释
                id={"one"}
                /*注释*/
                className="a"
                data-id={"my"}>div</div>
        </div>
    )
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    root.render(vDOM);

4-10 原生事件复习

  • 如何增加事件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="http://www.baidu.com">百度</a>
<a href="http://www.alibaba.com" onclick="go(event,this)">阿里</a>
</body>
<script>
    // 为元素增加事件的两种试:
    // 1- 先获取元素,然后为元素增加事件
    document.querySelector("a").onclick = function (e) {
        alert(this.innerText)
        // return false;// 取消默认行为
        e.preventDefault();// 取消默认行为
    }
    // 2- 先定义一个函数,然后在DOM元素中增加属性”on事件名=‘事件处理函数()’“
    function go(e,_this){
        // alert(this);
        // alert(e);
        alert(_this.innerText)
        // return false;// 无法取消默认行为
        e.preventDefault();// 可以取消默认行为
    }
</script>
</html>
  • 事件有一些注意事项(this)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <button>0</button>
    <button>1</button>
    <button onclick="obj.fn2()">2</button>
    <button onclick="fn3()">3</button>
    <button onclick="obj.fn4()">4</button>
    <button>5</button>
    <button onclick="fn6()">6</button>
    <button onclick="obj.fn7()">7</button>
    <button onclick="obj.fn8()">8</button>
    <button onclick="box.fn9()">9</button>
    <button onclick="fn10()">10</button>
    <button onclick="fn11()">11</button>
</body>
<script>
    // "use strict";
    // 关注事件当中this代表的是什么
    const btns = document.querySelectorAll("button");
    btns[0].onclick = function(){
        console.log(this);//  <button>0</button>
    }
    btns[1].onclick = ()=>{
        console.log(this);// window
    }
    const obj = {
        fn2(){
            console.log(this===obj);// obj
        },
        fn3(){
            console.log(this);// window
        },
        fn4:()=>{
            console.log(this);// window
        },
        fn7(){
            "use strict"
            console.log(this);// obj
        },
        fn8:()=>{
            "use strict"
            console.log(this);//window
        },
        fn11(){
            "use strict"
            console.log(this);
        }
​
    }
    const fn3 = obj.fn3;
    const fn11= obj.fn11;
    btns[5].onclick = function(){
        "use strict"
        console.log(this);//  <button>5</button>
    }
​
    function fn6(){
        "use strict"
        console.log(this);// undefined
    }
    class  Box{
        fn9(){
            console.log(this===box);// box
        }
        fn10(){
            console.log(this);// undefined
        }
    }
    const box = new Box()
    const fn10 = box.fn10;
​
​
</script>
</html>

4-11- jsx中的事件

    // jsx中定义事件与原生中定义事件的区别
    // 1- 原生中定义事件全部小写(onclick),jsx中需要使用驼峰命名法(onClick)
    // 2- 原生中事件属性的值是一个字符串,而JSX中必须是一个函数。
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    function fn1(){
        console.log(this);// undefined
    }
    const fn2 = ()=>{
        console.log("fn2",this);// fn2 undefined
    }
    root.render((
        <div>
            <button onClick={fn1}>点我1</button>
            <button onClick={fn2}>点我2</button>
            <button onClick={()=>{
                console.log(this);// undefined
            }}>点我3</button>
            <button onClick={function(){
                console.log(this);// undefined
            }}>点我4</button>
        </div>
    ))
    // 不允许这么干:
    // const btn = document.querySelector("button");
    // console.log(btn);// null
    // btn.onclick = function(){
    //     console.log(1);
    // }

五- 类组件(了解)

React当中的组件分为:类组件(复杂组件,状态组件)与函数组件(简单组件,非状态组件)

类组件:通过class定义的组件称为类组件

函数组件:通过function定义的组件称为函数组件。

组件:组件是页面当中的一部分,一个页面可以由若干个组件组成。

1- 组件可以被多次使用。

2- 可以将功能进行抽离。在页面当中不必写过多的代码,可以提升代码阅读性。

组件的使用:

jsx进行解析时,如果遇到<,<的内容是以大写字母开头,会作为组件来使用。组件是需要进行定义的。

5.1- 类组件的基本用法

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    // 在定义类组件时,与React的约定:
    // 1- 的名字即是组件的名字,组件的名字首字母必须要大写
    // 2- 在定义类组件时,必须继承React.Component
    // 3- 类组件中必须要有render函数
    // 4- render函数的返回值即是要渲染的内容
    // 5- 组件在使用时,可以采用<组件名/>, <组件名></组件名>
    // 6- 组件可以使用多次
    {
        class App extends React.Component {
            render() {
                // return "字符串"
                // return (
                //     <div>
                //         <p>jsx</p>
                //     </div>
                // )
​
                // 如果返回的JSX有且只能是一个根元素
                return (
                    <>
                        <div>1</div>
                        <div>2</div>
                    </>
                )
            }
        }
​
        root.render((
            <div>
                <App/>
                <App></App>
            </div>
        ))
    }

5.3- 组合组件(组件嵌套)

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    const {Component} = React;
    class Child extends Component{
        render(){
            return (
                <div>
                    <h3>Child</h3>
                </div>
            )
        }
    }
    // App嵌套了组件Child
    // App组件是Child组件的父组件
    // Child组件是App组件的子组件
    class App extends React.Component {
        render() {
            return (
                <div>
                    <h3>App</h3>
                    <Child/>
                </div>
            )
        }
    }
​
    root.render((
        <>
            <App/>
            <Child></Child>
        </>
    ))

5.4- 组件传递属性

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    // 父子传递参数:通过属性。
    // 父组件需要传递:可以将参数作为调用子组件时的属性
    // 子组件需要接收:this.props
    class App extends React.Component{
        info={
            userName:"wangwu",
            age:900
        }
        render(){
            return (
                <div>
                    <h3>App</h3>
                    <Child userName={"zhangsan"}  age={12}></Child>
                    <Child userName={"lisi"}  age={800}></Child>
                    <Child userName={this.info.userName} age={this.info.age}></Child>
                    {/*以下写法与上方代码等值*/}
                    <Child {...this.info}></Child>
                </div>
            )
        }
    }
    class Child extends React.Component{
        // constructor(props) {
        //     super(props);
        //     console.log(this.props);// 得到的是一个对象,对象当中的属性即是父级设置的属性 {userName:"zhangsan",age:12}
        // }
        render(){
            return (
                <div>
                    <h3>Child</h3>
                    <p>userName:{this.props.userName}</p>
                    <p>age:{this.props.age}</p>
                </div>
            )
        }
    }
    root.render(<App/>)

5.5 属性不允许直接修改

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        render(){
            // this.props.userName = "lisi";// 只读属性不允许被修改
            let {userName,age} = this.props;
            userName = "lisi";
            return (
                <div>
                    <h3>APP</h3>
                    <p>userName:{this.props.userName}|{userName}</p>
                    <p>age:{this.props.age}|{age}</p>
                </div>
            )
        }
    }
    root.render(<App age={12} userName={"zhangsan"}/>)

5.6 属性是可以进行类型限制的

 // 属性是可以进行类型的限制的。
    // 1- 引入prop-types.js,可以通过该文件对组件接收的属性进行类型限制
    // 2- 为类组件增加静态属性propTypes(限制类型),defaultProps(可以设置默认值)
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        static propTypes = {
            // 类型必须是字符串
            userName: PropTypes.string,
            // 类型必须是数字,且不允许为空
            age:PropTypes.number.isRequired,
            sex:PropTypes.string.isRequired
            // func:函数  object:对象  array:数组
        }
        static defaultProps = {
            // 可以设置默认值
            sex:"男"
        }
        render(){
            return (
                <div>
                    <h3>APP</h3>
                    <p>userName:{this.props.userName}</p>
                    <p>age:{this.props.age}</p>
                    <p>sex:{this.props.sex}</p>
                </div>
            )
        }
    }
    root.render(<App age={12} userName={"zhangsan"}/>)

5.7- 传递参数的DEMO

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    const boyBook = [
        {
            id:1,
            bookName:"天龙八部",
            author:"金庸"
        },{
            id:2,
            bookName: "斗罗大陆",
            author: "唐家三少"
        }
    ]
    const girlBook = [
        {
            id:11,
            bookName:"还珠格格",
            author:"琼瑶"
        },{
            id:12,
            bookName: "情深深雨朦朦",
            author: "琼瑶"
        }
    ]
    {
        // 不使用组件
        // root.render((
        //     <div>
        //         <div>
        //             <h3 style={{color:"blue"}}>男生小说</h3>
        //             {
        //                 boyBook.map(item=>(
        //                     <p key={item.id}>{item.author}:《{item.bookName}》</p>
        //                 ))
        //             }
        //         </div>
        //         <div>
        //             <h3 style={{color:"pink"}}>女小说</h3>
        //             {
        //                 girlBook.map(item=>(
        //                     <p key={item.id}>{item.author}:《{item.bookName}》</p>
        //                 ))
        //             }
        //         </div>
        //     </div>
        // ))
    }
    {
        // 使用组件
        class BookList extends React.Component{
            static propTypes = {
                bookInfo:PropTypes.array.isRequired,
                title:PropTypes.string.isRequired
            }
            render(){
                return (
                    <div>
                        <h3 style={{color:this.props.color}}>{this.props.title}</h3>
                        {
                            this.props.bookInfo.map(item=>(
                                <p key={item.id}>{item.author}:《{item.bookName}》</p>
                            ))
                        }
                    </div>
                )
            }
        }
        root.render((
            <div>
                <BookList bookInfo={boyBook} color={"blue"} title={"男生小说"}></BookList>
                <BookList bookInfo={girlBook} color={"pink"} title={"女生小说"}></BookList>
            </div>
        ))
    }

5.8- 类组件事件this指向undefined的问题

  const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends  React.Component{
        clickHandler(){
            console.log(this);// undefined
        }
        render(){
            return (
                <div>
                    <h3>APP</h3>
                    <button onClick={function(){
                        console.log(this);// undefined
                    }}>点我呀</button>
                    <button onClick={this.clickHandler}>点我呀2</button>
                </div>
            )
        }
    }
    root.render((
        <App></App>
    ))

5.9- 解决类组件事件this指向undefined问题的四个方法

  • 将函数直接设置为箭头函数

    // 1- 将函数直接设置为箭头函数
        // 优点:方便
        // 缺点:不能复用,如果逻辑代码较多,阅读不方便
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        class App extends React.Component{
            render(){
                // console.log("render",this);
                return (
                    <div>
                        <h3>App</h3>
                        <button onClick={(e)=>{
                            console.log(this,e.target.innerText);
                        }}>你过来呀</button>
                    </div>
                )
            }
        }
        root.render((
            <App></App>
        ))
    
  • 可以将箭头函数在类中进行定义

    // 2- 可以将箭头函数在类中进行定义
      // 优点:可以被复用,代码多时也易于阅读
      // 缺点:函数在实例中定义,如果组件被多次使用,会占用内存。
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      class App extends React.Component{
        clickHandler=(e)=>{
            console.log(this,e.target.innerText);
        }
        render(){
          // console.log("render",this);
          return (
                  <div>
                    <h3>App</h3>
                    <button onClick={this.clickHandler}>你过来呀</button>
                  </div>
          )
        }
      }
      root.render((
          <App></App>
      ))
    
  • 可以通过bind将this进行指定(绑定)

    // 3-建议:可以通过bind将this进行指定(绑定)
      // 优点:传递参数方便,可以复用
      // 缺点:每次调用都要写bind
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      class App extends React.Component{
        clickHandler(a,b,c,d,e){
            console.log(this,e.target.innerText);
        }
        render(){
          // console.log("render",this);
          return (
                  <div>
                    <h3>App</h3>
                    <button onClick={this.clickHandler.bind(this, 1, 2, 3,4)}>你过来呀</button>
                  </div>
          )
        }
      }
      root.render((
              <App></App>
      ))
    
  • 在实例属性中进行绑定

      // 4 在实例属性中进行绑定
        // 缺点:组件被多次使用,每次使用均会在内存中创建实例属性clickHandler
        //      传递参数只能够进行一次指定,动态 指定参数不方便
        // 优点:当你在使用函数时,不需要再次写bind
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        class App extends React.Component{
            constructor(props) {
                super(props);
                this.clickHandler = this.clickHandler.bind(this,1,2,3,4);
            }
            clickHandler(a,b,c,d,e){
                console.log(this,a,b,c,d,e.target.innerText);
            }
            render(){
                // console.log("render",this);
                return (
                    <div>
                        <h3>App</h3>
                        <button onClick={this.clickHandler}>你过来呀</button>
                    </div>
                )
            }
        }
        root.render((
            <App></App>
        ))
    ​
    ​
    

5-10 状态(state)

  • 状态的定义与使用

    // 属性(props):在父级定义,属性是只读的,不允许修改
        // 状态(state):在组件中定义,是允许修改的。
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        class App extends React.Component{
            // 声明状态方式一
            state = {
                num:100
            }
            // constructor(props) {
            //     super(props);
            //     // 在实例属性中设置state,值必须是对象
            //     // 声明状态方式二
            //     // this.state = {
            //     //     num:0
            //     // }
            // }
            render(){
                return (
                    <div>
                        <h3>App</h3>
                        <button onClick={()=>{
                            // 状态修改的正途是通过this.setState进行更改
                            // 一旦执行this.setState,那么会再次执行render,会重新生成虚拟DOM,与上一次生成的虚拟DOM进行比较,将不同点进行更新。
    ​
                            // 修改状态一:
                            // 1- 状态通过setState是异步修改
                            // this.setState({
                            //     num:this.state.num+1
                            // })
                            // console.log(this.state.num);
    ​
                            // 2- this.setState第一个参数是对象,可以指定修改的状态。第二个参数是回调函数。
                            // 注意:不要用setState以外方法去更改状态
                            // this.setState({
                            //     num:this.state.num+1
                            // },function(){
                            //     // 当数据更新完毕且界面渲染完成之后执行回调。
                            //     console.log(this.state.num,document.querySelector("button").innerText);
                            // })
                            // console.log(this.state.num);
    ​
                            // 修改状态二:
                            this.setState(function(prevState,props){
                                // console.log(prevState,props);
                                console.log(this.state === prevState,this.props === props);//true true
                                return {
                                    num:prevState.num+1
                                }
                            },()=>{
                                console.log("回调函数",this.state.num)
                            })
                            console.log(this.state.num);
                        }}>{this.state.num}</button>
                    </div>
                )
            }
        }
        root.render((
            <App a={1} b={2}></App>
        ))
    
  • 同步情况:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!--    <script src="./lib/react.development.js"></script>-->
    <!--    <script src="./lib/react-dom.development.js"></script>-->
        <script src="./lib/babel.min.js"></script>
    </head>
    <body>
    <div id="root"></div>
    </body>
    <script type="text/babel">
        // 注意:在18之后都是异步,在18之前如果将this.setState写到定时器或promise中为同步的。
        class App extends React.Component{
            state ={
                userName:"zhangsan"
            }
            render(){
                return (
                    <div>
                        <h3>app</h3>
                        <button onClick={()=>{
                            // 异步情况:
                            this.setState({
                                userName:this.state.userName+"!"
                            })
                            console.log("异步",this.state.userName);
    ​
                            // 定时器或promise
                            // setTimeout(()=>{
                            //     this.setState({
                            //         userName:this.state.userName+"!"
                            //     })
                            //     console.log(this.state.userName);
                            // })
    ​
                            // Promise.resolve().then(value=>{
                            //         this.setState({
                            //             userName:this.state.userName+"!"
                            //         })
                            //         console.log(this.state.userName);
                            // })
                        }}>{this.state.userName}</button>
                    </div>
                )
            }
        }
    ​
        // console.log(ReactDOM)
        ReactDOM.render(<App/>,document.querySelector("#root"));
    ​
    </script>
    </html>
    

5-11 ref

通过ref的方式可以获取真实的DOM.

  • 与字符串结合使用

     const root = ReactDOM.createRoot(document.querySelector("#root"));
        // ref使用方式一:
        // 1- 可以为要操作的React元素上增加ref属性,其值是一个字符串
        // 2- 可以通过this.refs.字符串可以获得其真实DOM
        // 3- ref与组件结合使用时,可以获得组件实例。从而可以实现组件之间的通讯。
        // 注意:ref与组件结合使用时,那么在组件中通过this.props是得不到ref的。如果要向子组件传递参数,参数名不要写成ref.
        class App extends React.Component{
            render(){
                return (
                    <div>
                        <h3 ref={"title"}>App</h3>
                        <input ref={"keyword"} type="text"/>
                        <button onClick={()=>{
                            // 如果React元素上没有ref,那么this.refs的值为{}
                            // console.log(this.refs);// {}
    ​
                            // 在React元素中增加ref,this.refs会多一个属性,属性的名字即是React元素中ref的属性值。
                            // console.log(this.refs);// {keyword:}
                            // console.log(this.refs.keyword === document.querySelector("input"));
    ​
    ​
                            // console.log(this.refs);// {title:xxx,keyword:xxx}
                            // console.log(this.refs.title.innerText);// App
                            // console.log(this.refs.keyword.value)
    ​
                            // console.log(this.refs.child)// 得到的是子组件实例
                            console.log(this.refs.child.userName);
                            console.log(this.refs.child.state.age);
                            this.refs.child.setState({
                                age:this.refs.child.state.age+3
                            })
                        }}>点我呀</button>
                        <Child ref={"child"} a={1}></Child>
                    </div>
                )
            }
        }
        class Child extends React.Component{
            userName="zhangsan";
            state={
                age:12
            }
            render(){
                console.log("child",this.props)
                return (
                    <div>
                        <h3>Child</h3>
                        <button onClick={()=>{
                            this.setState({
                                age:this.state.age+2
                            })
                        }}>{this.state.age}</button>
                    </div>
                )
            }
        }
        root.render((
            <App></App>
        ))
    
  • ref与箭头函数结合使用

     // ref使用方式二:
        // React元素的属性ref的值如果是一个箭头函数,那么不可以通过this.refs获取其真实DOM
        // ref的值是箭头函数的特点:
        // 1- 当组件在挂载时,箭头函数会执行。
        // 2- 箭头函数接收的参数即是真实的DOM.
        // 3- 在使用时,需要将接收到的DOM作为当前实例的属性。
        const root = ReactDOM.createRoot(document.querySelector("#root"));
    ​
        class App extends React.Component {
            render() {
                return (
                    <div>
                        <h3 ref={el=>this.title=el}>App</h3>
                        <input type="text" ref={el=>{
                            // console.log("执行",el===document.querySelector("input"))
                            this.keyword = el;// 为当前实例增加属性keyword,值为真实DOM
                        }}/>
                        <button onClick={()=>{
                            console.log(this.keyword.value);
                            console.log(this.title.innerText);
                            console.log(this.child.userName);
                        }}>点我呀</button>
                        <Child ref={el=>this.child=el}></Child>
                    </div>
                )
            }
        }
        class Child extends React.Component{
            userName="zhangsan";
            render(){
                return (
                    <div>
                        <h3>CHILD</h3>
                    </div>
                )
            }
        }
    ​
        root.render((
            <App></App>
        ))
    
  • ref与React.createRef结合使用(推荐)

     // ref使用方式三:React.createRef
        // 1- 自定义一个实例属性,其值为React.createRef()
        // 2- 为React元素增加ref属性,其值设置为自定义的实例属性。
        // 3- 可以通过实例属性.current得到真实DOM
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        const {createRef} = React;
        class App extends React.Component {
            keyword = React.createRef();
            title=createRef();
            child = createRef();
            render() {
                console.log(this.keyword);// {current}
                return (
                    <div>
                        <h3 ref={this.title}>App</h3>
                        <input ref={this.keyword} type="text" />
                        <button onClick={()=>{
                            // console.log(this.keyword.current===document.querySelector("input"))
                            console.log(this.keyword.current.value);
                            console.log(this.title.current.innerText);
                            console.log(this.child.current.userName);
                            this.keyword.current.value = 100;
                        }}>点我呀</button>
                        <Child ref={this.child}></Child>
                    </div>
                )
            }
        }
        class Child extends React.Component{
            userName="zhangsan";
            render(){
                return (
                    <div>
                        <h3>CHILD</h3>
                    </div>
                )
            }
        }
    ​
        root.render((
            <App></App>
        ))
    

5.12- 受控组件

    // 表单当中的元素在使用时会划分为两种:受控组件,非受控组件。
    // 如果在表单元素中使用value,checked那么会受其值的控制。
    // 一旦组件受控,需要结合onChnage事件来更改value值
    // 对于表单的两种操作:1- 设置表单值,2-收集表单数据
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            userName:"zhangsan",
            passWord:"123456",
            city:2,
            sex:"1",
            hobby:["1","3"]
        }
        changeHandler(e){
            let data = e.target.value;
            // 优化
            if(e.target.type === "checkbox"){
                data = [...this.state[e.target.name]];// 复制hobby
                const index = data.findIndex(v=>v === e.target.value);
                if(index>-1) data.splice(index,1)
                else data.push(e.target.value);
            }
            this.setState({
                [e.target.name]:data
            })
​
​
​
​
            /*思路:
            * 除了多选之外,以下代码都支持
            * 判断元素是否为多选*/
            // console.log(e.target.type);
            // if(e.target.type === "checkbox"){
            //     // 判断状态当中是否包含e.target.value
            //     // 如果包含,在数组中移除该数据
            //     // 如果不包含,在数组中增加该数据。
            //     const data = [...this.state[e.target.name]];// 复制hobby
            //     const index = data.findIndex(v=>v === e.target.value);
            //     if(index>-1){
            //         data.splice(index,1);
            //         // 如果包含,在数组中移除该数据
            //         this.setState({
            //             [e.target.name]:data
            //         })
            //     }else{
            //         // 如果不包含,在数组中增加该数据。
            //         data.push(e.target.value);
            //         this.setState({
            //             [e.target.name]:data
            //         })
            //     }
            // }else{
            //     this.setState({
            //         [e.target.name]:e.target.value
            //     })
            // }
​
​
        }
        submitHandler(e){
            e.preventDefault();// 移除默认行为。
            console.log("用户名",this.state.userName);
            console.log("密码",this.state.passWord);
            console.log("城市",this.state.city);
            console.log("性别",this.state.sex);
            console.log("爱好",this.state.hobby)
        }
        render(){
            return (
                <div>
                    <h3>App</h3>
                    <form onSubmit={this.submitHandler.bind(this)}>
                        <p>用户名:<input name={"userName"} onChange={this.changeHandler.bind(this)} type="text" value={this.state.userName}/></p>
                        <p>密码:<input name={"passWord"} onChange={this.changeHandler.bind(this)} type="password" value={this.state.passWord}/></p>
                        <p>城市:
                            <select onChange={this.changeHandler.bind(this)} name="city" value={this.state.city}>
                                <option value="1">上海</option>
                                <option value="2">北京</option>
                                <option value="3">武汉</option>
                                <option value="4">西安</option>
                                <option value="5">深圳</option>
                                <option value="6">南白滩</option>
                            </select>
                        </p>
                        <p>性别:
                            <input name="sex" onChange={this.changeHandler.bind(this)} checked={this.state.sex==="1"} type="radio" value={"1"}/>男
                            <input name="sex" onChange={this.changeHandler.bind(this)} checked={this.state.sex==="2"} type="radio" value={"2"}/>女
                        </p>
                        <p>
                            爱好:
                            <input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("1")} value={"1"}/>学习
                            <input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("2")} value={"2"}/>抽烟
                            <input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("3")} value={"3"}/>喝酒
                            <input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("4")} value={"4"}/>烫头
                        </p>
                        <button>提交</button>
                    </form>
                </div>
            )
        }
    }
    root.render((
        <App></App>
    ))

5.13- 非受控组件

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    const {createRef} = React;
    class App extends React.Component{
        state = {
            userName:"wangwu",
            passWord:"123456",
            city:"6",
            sex:"2",
            hobby:["1","4"]
        }
        userName = createRef();
        passWord = createRef();
        city = createRef();
        sex = [createRef(),createRef()];
        hobby = [createRef(),createRef(),createRef(),createRef()];
        submitHandler(e){
            e.preventDefault();
            console.log("用户名",this.userName.current.value);
            console.log("密码",this.passWord.current.value);
            console.log("城市",this.city.current.value);
            console.log("性别",this.sex.find(v=>v.current.checked).current.value);
            console.log("爱好",this.hobby.filter(v=>v.current.checked).map(v=>v.current.value))
        }
        render(){
            return (
                <div>
                    <h3>App</h3>
                    <form onSubmit={this.submitHandler.bind(this)}>
                        <p>用户名:<input ref={this.userName} name={"userName"} defaultValue={this.state.userName}  type="text" /></p>
                        <p>密码:<input ref={this.passWord} name={"passWord"} defaultValue={this.state.passWord} type="password"/></p>
                        <p>城市:
                            <select  ref={this.city} name="city" defaultValue={this.state.city} >
                                <option value="1">上海</option>
                                <option value="2">北京</option>
                                <option value="3">武汉</option>
                                <option value="4">西安</option>
                                <option value="5">深圳</option>
                                <option value="6">南白滩</option>
                            </select>
                        </p>
                        <p>性别:
                            <input ref={this.sex[0]} name="sex" defaultChecked={this.state.sex==="1"}  type="radio" value={"1"}/>男
                            <input ref={this.sex[1]} name="sex" defaultChecked={this.state.sex==="2"}  type="radio" value={"2"}/>女
                        </p>
                        <p>
                            爱好:
                            <input name={"hobby"} ref={this.hobby[0]} defaultChecked={this.state.hobby.includes("1")}  type="checkbox" value={"1"}/>学习
                            <input name={"hobby"} ref={this.hobby[1]} defaultChecked={this.state.hobby.includes("2")}  type="checkbox" value={"2"}/>抽烟
                            <input name={"hobby"} ref={this.hobby[2]} defaultChecked={this.state.hobby.includes("3")}  type="checkbox" value={"3"}/>喝酒
                            <input name={"hobby"} ref={this.hobby[3]} defaultChecked={this.state.hobby.includes("4")}  type="checkbox" value={"4"}/>烫头
                        </p>
                        <button>提交</button>
                    </form>
                </div>
            )
        }
    }
    root.render((
        <App></App>
    ))

六- 生命周期

生命周期指的是类组件。类组件从创建到销毁的过程称其为该组件的生命周期。

在组件的生命周期中,所暴露出来(在阶段中所自动执行)的函数称为钩子函数。

6.1- 生命周期旧

6.1.1- 挂载阶段

 // 在组件挂载阶段暴露出来的钩子函数,除 render以外,均只运行一次。
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            num:1
        }
        constructor(props) {
            super(props);
            // 实例化
            console.log("1-constructor");
        }
        UNSAFE_componentWillMount(){
            // 在挂载之前
            console.log("2-UNSAFE_componentWillMount",document.querySelector("h3"),this.state.num);
            this.state.num = 100;
        }
        render(){
            // 指定要挂载的内容
            console.log("3-render");
            return (
                <div>
                    <h3>App</h3>
                    <button onClick={()=>{
                        this.state.num = 1000;
                    }}>{this.state.num}</button>
                </div>
            )
        }
        componentDidMount(){
            // 挂载结束之后
            console.log("4-componentDidMount",document.querySelector("h3"))
        }
    }
    root.render((
        <App></App>
    ))

6.1.2- 更新属性阶段

const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            a:1,
            b:2
        }
        render(){
            return (
                <div>
                    <h3>App</h3>
                    <button onClick={()=>{
                        this.setState({
                            a:this.state.a+1
                        })
                    }}>更改状态a</button>
                    <Child {...this.state}></Child>
                </div>
            )
        }
    }
    class Child extends React.Component{
        // constructor(props) {
        //     super(props);
        //     // console.log("1-constructor");
        // }
        // UNSAFE_componentWillMount(){
        //     console.log("2-UNSAFE_componentWillMount");
        // }
        UNSAFE_componentWillReceiveProps(nextProps){
            console.log("1-UNSAFE_componentWillReceiveProps",this.props.a,nextProps.a)
        }
        shouldComponentUpdate(nextProps){
            // 当要更新属性时,执行到该钩子,this.props还未更新。接收的形参nextProps是即将要更新的属性
            console.log("2-shouldComponentUpdate",this.props.a,nextProps.a)
            if(nextProps.a<5) return true;
            return false;
        }
        UNSAFE_componentWillUpdate(nextProps){
            console.log("3-UNSAFE_componentWillUpdate",this.props.a,nextProps.a)
        }
        render(){
            console.log("4-render",this.props.a)
            return (
                <div>
                    <h3>Child</h3>
                    <p>a:{this.props.a}</p>
                    <p>b:{this.props.b}</p>
                </div>
            )
        }
        componentDidUpdate(prevProps){
            console.log("5-componentDidUpdate",this.props.a,prevProps.a);
        }
        // componentDidMount(){
        //     console.log("4-componentDidMount")
        // }
    }
    root.render((
        <App></App>
    ))

6.1.3- 更新状态阶段

 const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            num:1
        }
        shouldComponentUpdate(nextProps,nextState){
            // 状态还未更新。nextState是即将要更新的状态。
            // console.log(this.state.num,nextState.num)
            console.log("1-shouldComponentUpdate")
            if(nextState.num<5) return true;
            return false;
        }
        UNSAFE_componentWillUpdate(nextProps,nextState){
            console.log("2-UNSAFE_componentWillUpdate",this.state.num,nextState.num)
        }
        render(){
            console.log("3-render",this.state.num)
            return (
                <div>
                    <h3>App</h3>
                    <button onClick={()=>{
                        // this.setState({
                        //     num:this.state.num+1
                        // })
​
                        this.state.num = 100;
                        this.forceUpdate();// 强制更新
                    }}>{this.state.num}</button>
                </div>
            )
        }
        componentDidUpdate(prevProps,prevState){
            console.log("4-componentDidUpdate",this.state.num,prevState.num)
        }
    }
    root.render((
        <App></App>
    ))

6.1.4- 卸载阶段

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
    <script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            isShow:true
        }
        changeIsShow(){
            this.setState({
                isShow:!this.state.isShow
            })
        }
        render(){
            return (
                <div>
                    <button onClick={this.changeIsShow.bind(this)}>显示与隐藏</button>
                    {
                        this.state.isShow && <Child/>
                    }
                </div>
            )
        }
    }
    class Child extends React.Component{
        state={
            num:1
        }
        constructor(props) {
            super(props);
            console.log("挂载1-》constructor")
        }
        UNSAFE_componentWillMount(){
            console.log("挂载2-》UNSAFE_componentWillMount")
        }
        render(){
            console.log("挂载3-》render")
            return (
                <div style={{width:200,height:200,border:"1px solid yellow"}}>
                    <h3>{this.state.num}</h3>
                </div>
            )
        }
        componentDidMount(){
            console.log("挂载4->componentDidMount");
            this.timer =  setInterval(()=>{
                console.log("setInterval",this.state.num);
                this.setState({
                    num:this.state.num+1
                })
            },1000)
        }
        componentWillUnmount(){
            clearInterval(this.timer);// 清除定时器
            console.log("componentWillUnmount,当组件卸载之前执行");
        }
    }
    root.render((
        <App></App>
    ))
</script>
</html>

6.2- 生命周期新

    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        state = {
            userName:"zhangsan"
        }
        render(){
            return (
                <div>
                    <button onClick={()=>{
                       this.setState({
                           userName:this.state.userName+"!"
                       })
                    }}>点我</button>
                    <Child userName={this.state.userName}></Child>
                </div>
            )
        }
    }
    class Child extends React.Component{
        state = {
            a:1,
            b:2,
            userName:"lisi"
        }
        // 作用:可以将接收到属性与当前的数据状态进行合并。
        // 在什么时候执行:
        //     1- 组件挂载时会运行。
        //     2- 父组件执行render后会执行。
        //     3- 在当前组件中执行this.setState
        //     4- 强制更新
        // 特点:
        //     1- 组件中必须定义state
        //     2- 必须要有返回值,且返回值必须是对象或null
        //     3- 如果返回的对象中的属性与当前状态的属性冲突,那么会将数据状态进行覆盖
        //     4- getDerivedStateFromProps是一个静态方法,该方法的this是undefined
        static getDerivedStateFromProps(props){
            console.log(props,this);
            return {
                // a:100,
                c:3,
                d:4,
                ...props
            }
        }
        render(){
            return (
                <div>
                    <h3>Child</h3>
                    <button onClick={()=>{
                        // this.forceUpdate();// 强制更新
                        this.setState({
                            a:this.state.a+1
                        })
                    }}>{this.state.a}</button>
                </div>
            )
        }
        // 注意:
        // 1- getSnapshotBeforeUpdate需要与componentDidUpdate结合使用
        // 2- getSnapshotBeforeUpdate它的返回值,会作为componentDidUpdate的第三个参数
        // 3- 执行该钩子时,状态值已经发生变化
        // 4- 当前的DOM还未更新。
        // 在此处可以生成日志信息。
        getSnapshotBeforeUpdate(prevProps,prevState){
            // prevState:状态更新之前的值
            // this.state:当前状态值
            console.log(prevState.a,this.state.a);
            console.log(document.querySelectorAll("button")[1].innerText)
            return 100;
        }
        componentDidUpdate(prevProps,prevState,snapshot){
            console.log("更新完毕!componentDidUpdate",snapshot)
        }
    }
    root.render((
        <App></App>
    ))
​

五、react-cli 脚手架(重点)

5.1- 介绍

  • React脚手架: 用来帮助程序员快速创建一个基于React库的模板项目。
  • 脚手架是开发现代Web应用的必备。
  • 充分使用Webpack、Babel、ESLint等工具辅助项目开发。
  • 零配置,无需手动配置繁琐的工具即可使用。
  • 让程序可以关注业务,而不是工具配置

5.2- 创建项目并启动

  • 创建项目:注意:项目名称不能包含大写字母;不要写中文;项目所在的目录不允许出现中文以及特殊字符。

    npx create-react-app one  #创建一个名字为one的项目
    
  • 成功界面

  • 启动项目

    cd one;
    npm start # 启动项目
    
  • npx命令介绍:

    • npx是 npm v5.2.0引入的一条命令

    • 目的:提升包内提供的命令行工具的使用体验。

    • 之前:需要全局安装脚手架包,再使用这个包中提供的命令。

      npm install -g create-react-app// 全局安装一次
      create-react-app hello-react  //创建项目
      cd hello-react
      npm start
      
    • 现在:无需安装全局安装脚手架包,就可以直接使用这个包提供的命令。

  • 安装vscode插件

5.3-react脚手架项目结构

  • 项目结构:

目录结构分析:

one
|-public
|    |-favicon.ico  // 应用程序顶部的icon图标
|    |-index.html   // 应用程序index.html入口文件 
|    |-logo192.png  // 被在mainfest.json中使用
|    |-logo512.png  // 被在mainfest.json中使用 
|    |-manifest.json    // 和web app 配置相关
|    |-robots.txt   // 指定搜索引擎可以或者无法爬取哪些文件
|-src
|    |-App.css  // App组件相关样式
|    |-App.js   // App组件的代码文件 
|    |-App.tes.js   // App组件的测试代码文件
|    |-index.css    // 全局的样式文件
|    |-index.js // 整个应用程序的入口文件
|    |-logo.svg // 启动项目,所看到的React图标
|    |-reportWebVitals.js// 提供各种质量信号的统一指南
|    |-setupTest.js // 测试初始文件 
|-gitgnore // git排除文件配置
|-package.json// 对整个应用程序的描述:包括应用名称,版本号,依赖包,以及项目的启动打包等等
|-README.md // readme说明文档

5-4 初次使用脚手架

  • 清除src下的所有文件

  • 在src中增加index.js

    import React from "react";// 引入React核心模块
    import ReactDOM from "react-dom/client";// 用于操作DOM的模块
    // 生成React容器,赋值给常量root
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    class App extends React.Component{
        render(){
            return (
                <div>
                    <h3>App</h3>
                </div>
            )
        }
    }
    root.render(<App/>)
    

5-5 使用组件

  • src->App.jsx

    // 1- 先定义再导出
    // import React from "react";
    // class App extends React.Component{
    //     render(){
    //         return (
    //             <div>
    //                 <h3>App1</h3>
    //             </div>
    //         )
    //     }
    // }
    // export default App;// 2- 可以直接导出
    // import React from "react";
    // export default class App extends React.Component{
    //     render(){
    //         return (
    //             <div>App2</div>
    //         )
    //     }
    // }// 3- 定义组件
    // import React from "react";
    // const {Component} = React;
    // export default class App extends Component{
    //     render(){
    //         return (
    //             <div>App3</div>
    //         )
    //     }
    // }// 4- 定义组件(常见)
    // import React,{Component} from "react";// export
    // export default class App extends Component{
    //     render(){
    //         return (
    //             <div>App3</div>
    //         )
    //     }
    // }// 5- 默认导出APP
    import React,{Component} from "react";// export
    export class My extends Component{
        render(){
            return (
                <div>My</div>
            )
        }
    }
    export default class App extends Component{
        render(){
            return (
                <div>App3</div>
            )
        }
    }
    
  • src->index.js

    import React from "react";// 引入React核心模块
    import ReactDOM from "react-dom/client";// 用于操作DOM的模块import App,{My} from "./App";
    ​
    // 生成React容器,赋值给常量root
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    root.render((
        <>
            <App/>
            <My></My>
        </>
    ))
    

5-6 函数组件(重点)

5-6-1- 函数组件的基本使用

  • App.jsx

    // vscode: rfc
    // webstorm:rsf(function) rsc(箭头函数)
    import React,{Fragment} from 'react';
    const bookInfo = [{
        id:1,
        bookName:"天龙八部",
        author:"金庸"
    },{
        id:2,
        bookName: "圆月弯刀",
        author: "古龙"
    }];
    function App(){
        // return (
        //     <>
        //         {
        //             bookInfo.map(item=>(
        //                 <React.Fragment key={item.id}>
        //                     <h3>{item.author}</h3>
        //                     <p>{item.bookName}</p>
        //                 </React.Fragment>
        //             ))
        //         }
        //     </>
        // )
        return (
            <>
                {
                    bookInfo.map(item=>(
                        <Fragment key={item.id}>
                            <h3>{item.author}</h3>
                            <p>{item.bookName}</p>
                        </Fragment>
                    ))
                }
            </>
        )
    ​
    }
    ​
    export default App;
    
  • src->index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
​
/*函数组件的约定:
 * 约定1:函数组件是通过函数来定义的
 * 约定2:函数的名字必须大写,函数的名字即是组件的名字
 * 约定3:在使用时,将函数的名字作为标签名即可
 * 约定4:函数必须要有返回值,返回的内容即是渲染的内容
 * 约定5:函数如果返回的是null,undefined,false,true不会渲染内容
 * 约定6:函数组件如果返回的是JSX,有且只能是一个根元素。*/
{
    // 定义函数组件1
    // function App(){
    //     // return null;
    //     // return false;
    //     // return true;
    //     // return undefined;
    //     return (
    //         <h3>App</h3>
    //     )
    // }
    // root.render(<App/>)
}
{
    // 定义函数组件2(定义为箭头函数)
    // const App = ()=>{
    //     return (
    //         <h3>App2</h3>
    //     )
    // }
    // root.render(<App/>)
}
{
    // 定义的函数返回的内容有且只能是一个根元素,以下写法不正确
    // function App(){
    //     return (
    //         <div>1</div>
    //         <div>2</div>
    //     )
    // }
    // root.render(<App/>)
}
{
    // 可以通过包裹标签(<></>)解决上一个示例的问题
    // function App(){
    //     return (
    //         <>
    //             <div>1</div>
    //             <div>2</div>
    //         </>
    //     )
    // }
    // root.render(<App/>)
}
{
    // <></>是包裹标签,不会进行渲染。
    // <></>在map时,不可以作为包裹标签。因为无法增加属性key
    // React.Fragment也是包裹标签。
    // React.Fragment与<></>的区别:React.Fragment可以用在map回调中,React.Fragment可以增加属性key
    // const bookInfo = [{
    //     id:1,
    //     bookName:"天龙八部",
    //     author:"金庸"
    // },{
    //     id:2,
    //     bookName: "圆月弯刀",
    //     author: "古龙"
    // }];
    // function App(){
    //     return (
    //         <>
    //             {
    //                 bookInfo.map(item=>(
    //                     <React.Fragment key={item.id}>
    //                         <h3>{item.author}</h3>
    //                         <p>{item.bookName}</p>
    //                     </React.Fragment>
    //                 ))
    //             }
    //         </>
    //     )
    // }
    // root.render(<App/>)
}
{
​
    // 将函数组件放置到外部
    root.render(<App/>)
}

5-6-2- 函数组件进行嵌套,组件也可以使用多次

import React from "react";
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
    // 函数组件是可以多次使用的
    // function App(){
    //     return (
    //         <div>
    //             <h3>App</h3>
    //         </div>
    //     )
    // }
    // root.render((
    //     <>
    //         <App/>
    //         <App/>
    //         <App/>
    //         <App/>
    //         <App/>
    //     </>
    // ))
}
{
    // 函数组件是可以进行嵌套的(组合)
    function App(){
        return (
            <div>
                <h3>App</h3>
                <Child></Child>
            </div>
        )
    }
    function Child(){
        return (
            <div>
                <h3>Child</h3>
            </div>
        )
    }
    root.render((
        <>
            <App/>
            <Child/>
        </>
    ))
}

5-6-3- 函数组件抽离

  • src->index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import Child from "./Child";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((
    <>
        <App></App>
        <Child></Child>
    </>
))
​
​
  • src->App.jsx
import React from 'react';
import Child from "./Child";
function App(props) {
    return (
        <div>App
            <Child></Child>
            <Child></Child>
            <Child></Child>
            <Child></Child>
        </div>
​
    );
}
export default App;
  • src->Child.jsx
import React from 'react';
​
function Child(props) {
    return (
        <div>Child</div>
    );
}
export default Child;

5-6-4- 函数组件传递属性

  • src->index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App/>)
  • src->App.jsx
import React from 'react';
import Child from "./Child";
function App(props) {
    return (
        <div>
            <Child userName={"zhangsan"} age={12}></Child>
        </div>
    );
}
export default App;
  • src->Child.jsx
import React from 'react';
// 可以通形参props来接收属性。形参的名字也可以自定义
// props的值是一个对象,所以可以进行解构赋值
function Child(props) {
    // console.log(props);
    // 将对象props解构赋值给常量userName,age
    const {userName,age} = props;
    return (
        <div>Child
            <p>userName:{props.userName}|{userName}</p>
            <p>age:{props.age}|{age}</p>
        </div>
    );
}
​
export default Child;

5-6-5- 函数组件对属性类型限制

  • App.jsx

    import React from 'react';
    import Child from "./Child";
    import My from "./My"
    const state = {
        userName:"zhangsan",
        age:12
    }
    function App(props) {
        return (
            <div>
                <Child userName={state.userName} age={state.age}></Child>
                <hr/>
                <Child {...state}></Child>
                <hr/>
                <Child  age={18}></Child>
                <hr/>
                <My userName={"zhangsan"} age={18}></My>
            </div>
        );
    }
    export default App;
    
  • Child.jsx

import React from 'react';
import PropTypes from "prop-types";
// 函数可以直接增加属性。称为函数的静态属性
Child.propTypes = {
    // userName属性必须是字符串,且不允许为空(undefined)
    userName:PropTypes.string.isRequired,
    // age属性必须是数字
    age:PropTypes.number
    // PropTypes.object 对象
    // PropTypes.array  数组
    // PropTypes.func   函数
}
// 可以设置默认值
Child.defaultProps = {
    userName:"zhaoliu"
}
function Child(props) {
    const {userName,age} = props;
    return (
        <div>Child
            <p>userName:{props.userName}|{userName}</p>
            <p>age:{props.age}|{age}</p>
        </div>
    );
}
​
export default Child;
  • My.jsx(了解)
import React, {Component} from 'react';
import PropTypes from "prop-types";
class My extends Component {
    // static propTypes = {
    //     userName:PropTypes.string.isRequired,
    //     age:PropTypes.number.isRequired
    // }
    // static defaultProps = {
    //     userName:"zhangpeiyue",
    //     age:16
    // }
    render() {
        return (
            <div>
                <p>userName:{this.props.userName}</p>
                <p>age:{this.props.age}</p>
            </div>
        );
    }
}
My.propTypes = {
    userName:PropTypes.string.isRequired,
    age:PropTypes.number.isRequired
}
My.defaultProps = {
    userName:"zhangpeiyue",
    age:16
}
export default My;

5-6-6- 渲染属性(render-props)

  • app.jsx
import React, {Fragment} from 'react';
import BookList from "./components/BookList";
// 渲染属性:向组件传递的是一个函数,函数的名字一般设置为render,render会返回JSX
// 接收属性的组件中可以直接调用render
function App(props) {
    const boyBook = [
        {
            id:1,
            bookName:"大明:我重生成了朱允炆",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg"
        },
        {
            id:2,
            bookName:"模拟修仙传",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/a3/5e/a35e28be78d8f6832f62386eb0abfe0d.jpeg"
        }
    ];
    const girlBook = [
        {
            id:11,
            bookName:"王妃重生后忙着搞事业",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/b6/0d/b60d0b4f52d17b6e579de4c218883597.jpeg"
        },
        {
            id:12,
            bookName:"权宠娇娘",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/99/57/995725bacaef47e5a481d9b58555c261.png"
        },
    ]
    return (
        <div>
            <BookList render={()=>(
                <h3 style={{color:"blue"}}>男生必读</h3>
            )} bookInfo={boyBook}></BookList>
            <hr/>
            <BookList render={()=>(
                <h3 style={{color:"pink"}}>女生爱看</h3>
            )} bookInfo={girlBook}></BookList>
        </div>
    );
}
​
export default App;
  • components->BookList.jsx
import React, {Fragment} from 'react';
import PropTypes from "prop-types";
BookList.propTypes = {
    // 接收的参数是数组,且不允许为空
    bookInfo:PropTypes.array.isRequired,
    // 接收的参数是一个函数,且不允许为空
    render:PropTypes.func.isRequired
}
BookList.defaultProps = {
    bookInfo:[]
}
function BookList(props) {
    return (
        <div>
            {props.render()}
            {
                props.bookInfo.map(item=>(
                    <Fragment key={item.id}>
                        <img src={item.bookImg} alt=""/>
                        <p>{item.bookName}</p>
                    </Fragment>
                ))
            }
        </div>
    );
}
​
export default BookList;

5-6-7- children

  • src->App.jsx
import React, {Fragment} from 'react';
import BookList from "./components/BookList";
// 创建的组件在使用时,可以为其包裹内容。
// 包裹的内容如果是JSX,那么props.children得到的是React元素(虚拟DOM元素)
// 如果包裹的是多个并列关系的元素,那么props.children得到的是一个数组
// 在子组件中,可以通过props.children接收包裹的内容
function App(props) {
    const boyBook = [
        {
            id:1,
            bookName:"大明:我重生成了朱允炆",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg"
        },
        {
            id:2,
            bookName:"模拟修仙传",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/a3/5e/a35e28be78d8f6832f62386eb0abfe0d.jpeg"
        }
    ];
    const girlBook = [
        {
            id:11,
            bookName:"王妃重生后忙着搞事业",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/b6/0d/b60d0b4f52d17b6e579de4c218883597.jpeg"
        },
        {
            id:12,
            bookName:"权宠娇娘",
            bookImg:"https://static.zongheng.com/upload/s_image/cover/99/57/995725bacaef47e5a481d9b58555c261.png"
        },
    ]
    return (
        <div>
            <BookList bookInfo={boyBook}>
                <h3 style={{color:"blue"}}>男生必读</h3>
            </BookList>
            <hr/>
            <BookList bookInfo={girlBook}>
                <h3 style={{color:"pink"}}>女生爱看</h3>
            </BookList>
        </div>
    );
}
​
export default App;
  • src->components->BookList.jsx
import React, {Fragment} from 'react';
import PropTypes from "prop-types";
BookList.propTypes = {
    // 接收的参数是数组,且不允许为空
    bookInfo:PropTypes.array.isRequired
}
BookList.defaultProps = {
    bookInfo:[]
}
function BookList(props) {
    // console.log(props.children);
    return (
        <div>
            {
                props.children
            }
            {
                props.bookInfo.map(item=>(
                    <Fragment key={item.id}>
                        <img src={item.bookImg} alt=""/>
                        <p>{item.bookName}</p>
                    </Fragment>
                ))
            }
        </div>
    );
}
​
export default BookList;

5-6-8- 使用图片

import React from 'react';
import vue from "./assets/img/vue.png";
import react from "./assets/img/react.png";
const vue2 = require("./assets/img/vue.png");
const react2 = require("./assets/img/react.png");
// 1- 如果图片来自于其它服务,直接写完整地址
// 2- 如果图片来自于本项目,那么可以通过import引入图片
// 3- 如果图片来自于本项目,那么也可以通过require
function App(props) {
    console.log(require("./assets/img/vue.png"));
    return (
        <div>
            {/*来自其它服务的图片*/}
            <img src="https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg" alt=""/>
            <img src={vue} alt=""/>
            <img src={react} alt=""/>
            <img src={require("./assets/img/vue.png")} alt=""/>
            <img src={require("./assets/img/react.png")} alt=""/>
            <img src={vue2} alt=""/>
            <img src={react2} alt=""/>
        </div>
    );
}
​
export default App;

5-6-9- 使用样式

  • src->App.module.css

    #appContainer .cl{
        color:red;
    }
    
  • src->App.jsx

import React from 'react';
import Child from "./components/Child";
import styles from "./App.module.css";
// import "./App.css"
// 可以使用样式的模块化
// 1- 样式文件的名字需要以.module.css结尾
// 2- 引入import styles from "./App.module.css";
// 3- 使用 className={styles.cl}
function App(props) {
    return (
        <div>
            <p className={styles.cl}>app</p>
            <Child></Child>
        </div>
    );
}
​
export default App;

5-6-10- 组件拆分

  • App.jsx
import React from 'react';
import "./App.css";
import TodoHeader from "./components/TodoHeader";
import TodoMain from "./components/TodoMain";
import TodoFooter from "./components/TodoFooter";
function App(props) {
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                {/*头部*/}
                <TodoHeader/>
                {/*列表*/}
                <TodoMain/>
                {/*底部*/}
                <TodoFooter/>
            </div>
        </div>
    );
}
export default App;
  • src->components->TodoHeader.jsx
import React from 'react';
​
function TodoHeader(props) {
    return (
        <div className="todo-header">
            <input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
        </div>
    );
}
​
export default TodoHeader;
  • src->components->TodoMain.jsx
import React from 'react';
​
function TodoMain(props) {
    return (
        <ul className="todo-main">
            <li>
                <label>
                    <input type="checkbox"/>
                    <span>xxxxx</span>
                </label>
                <button className="btn btn-danger" style={{display:"none"}}>删除</button>
            </li>
            <li>
                <label>
                    <input type="checkbox"/>
                    <span>yyyy</span>
                </label>
                <button className="btn btn-danger" style={{display:"none"}}>删除</button>
            </li>
        </ul>
    );
}
​
export default TodoMain;
  • src->components->TodoFooter.jsx
import React from 'react';
​
function TodoFooter(props) {
    return (
        <div className="todo-footer">
            <label>
                <input type="checkbox"/>
            </label>
            <span>
          <span>已完成0</span> / 全部2
        </span>
            <button className="btn btn-danger">清除已完成任务</button>
        </div>
    );
}
​
export default TodoFooter;

六、 React Hooks(重点)

6.1-Hooks介绍

官方介绍:

Hooks是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hooks 是一些特殊的函数,它可以让你“钩入” React 的特性。 hooks函数是以 use开头。

如何理解:

Hooks的单词意思为“钩子”。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。而React Hooks 就是我们所说的“钩子”。

React Hooks就是加强版的函数组件,降低了开发难度,我们可以完全不使用 class,就能写出一个全功能的组件。

6.2- 为什么要使用hooks

可以不用使用高阶组件依然可以进行功能复用了。

没有state,没有生命周期,维护起来比class组件简单N倍。

省去this的指向问题的学习。

避免开始写成functional组件,因为需要状态,又改成了class的成本问题。

……

6.3- 什么时候用 Hooks

官方: 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hooks。

6.4- useState(常用)

useState是Hooks之一,可以让函数组件拥有类似与类组件中的state.

  • 购物车加减操作

    import React from 'react';
    // 1- useState是React模块提供的一个方法
    // 2- useState返回的是一个数组,接收的参数即是初始状态值。
    // 3- 返回的数组有两个元素,第一个元素即是状态,第二个元素是修改状态的方法。
    // 4- 当通过setXXX修改状态之后,函数会重新执行,注意:再次执行时,状态的值为上一次的结果(useState拥有记忆性)
    // 5- 修改状态的方法规范:以set开头,后续加上状态名,使用驼峰命名法
    function App(props) {
        // 创建一个初始值为1的数据状态
        // console.log(React.useState(1));
        // 通过数组的解构赋值进行接收状态(num),以及修改状态的方法(setNum)
        const [num,setNum] = React.useState(1);
        return (
            <div>
                <button onClick={()=>{
                    if(num>1){
                        setNum(num-1)
                    }
                }}>-</button>
                {num}
                <button onClick={()=>{
                    setNum(num+1);
                }}>+</button>
            </div>
        );
    }
    ​
    export default App;
    
  • 注意:如果状态是引用类型,那么需要复制,然后再进行操作

    import React,{useState} from 'react';
    ​
    function App(props) {
        // 创建一个初始值为100,名字为num的状态,修改状态的方法为setNum
        const [num,setNum] = useState(100);
        const [arr,setArr] = useState([1,2,3,4])
        return (
            <div>
                <button onClick={()=>{
                    const copyArr = [...arr];
                    copyArr.push(arr.length+1);
                    setArr(copyArr);// 传入的参数要保证与修改的状态的引用地址不同。否则界面不会更新
                }}>为数组增加数据</button>
                {
                    arr.map((item,index)=>(
                        <h3 key={index}>{item}</h3>
                    ))
                }
            </div>
        );
    }
    ​
    export default App;
    

6.5- useCallback

作用:记忆函数

import React, {useState,useCallback} from 'react';
// useCallback:可以将函数进行缓存处理。
// useCallback函数,接收的第一个参数是一个函数,第二个参数是数组
// 如果使用了useCallback,第二个参数建议声明,如果不声明相当于useCallback无用的方法。
// 如果省略第二个参数,不会有记忆。
// 如果只是写[],代表着该函数会进行记忆(缓存)
// 如果写的[num],代表着num发生变化,不会设置num缓存(保证num是最新的状态)。
function App(props) {
    const [num,setNum] = useState(1);
    const plus = useCallback(()=>{
        setNum(num+1);
    },[num])
    const subtract = useCallback(()=>{
        if(num>1){
            setNum(num-1)
        }
    },[num])
    return (
        <div>
            <button onClick={subtract}>-</button>
            {num}
            <button onClick={plus}>+</button>
        </div>
    );
}
​
export default App;

6.6- useMemo

作用:记忆结果。

import React, {useState,useCallback,useMemo} from 'react';
// useCallback记忆的是函数,useMemo记忆的结果(将接收函数执行以后的结果进行返回)
// useMemo第二个参数是数组。
// 如果将第二个参数设置为空数组,那么所得到值不会发生变化。
// 如果将第二个参数省略,那么不会产生记忆。
// 如果将第二个参数设置为[num],只有状态num发生变化,才会重新调用指定的函数,得到新的结果。
function App(props) {
    const [num,setNum] = useState(1);
    const plus = useCallback(()=>{
        setNum(num+1);
    },[num])
    const subtract = useCallback(()=>{
        if(num>1){
            setNum(num-1)
        }
    },[num])
​
    const result = useMemo(()=>{
        return (
            <div>
                <button onClick={subtract}>-</button>
                {num}
                <button onClick={plus}>+</button>
            </div>
        )
    },[num])
    return result;
}
​
export default App;

6.7- useRef(常用)

import React, {useState,useRef} from 'react';
// 1- 引入useRef
// 2- 将useRef函数运行的结果赋值给常量。
// 3- 将生成的常量作为操作的React元素的ref属性值
// 4- 在使用时,可以通过常量名.current可以得到真实的DOM。
function App(props) {
    const [num,setNum] = useState(1);
    const btn = useRef();
    const inputRef = useRef();
    return (
        <div>
            <input ref={inputRef} type="text"/>
            <button ref={btn} onClick={()=>{
                // console.log(btn.current === document.querySelector("button"))
                console.log(btn.current.innerText);
                console.log(inputRef.current.value)
            }}>{num}</button>
        </div>
    );
}
​
export default App;

6.8- useContext

useContext可以帮助我们解决跨组件传递数据,实现数据的共享。

  • src->context->index.js

    import {createContext} from "react";
    export default createContext(0);
    
  • App.jsx

    import React, {useState} from 'react';
    import Child from "./components/Child";
    import globalContext from "./context";
    /*useContext的使用流程:
    * 1-要通过React.createContext方法生成一个数据(对象)
    *     1-1 createContext需要在src下创建一个目录context,在context中新建文件index.js进行执行。
    *     1-2 createContext是一个函数,接收的数据是共享状态的初始值,返回的是一个对象
    *     1-3 对象当中拥有Provider(提供者),Consumer(消费者)
    * 2-如果需要在Child组件中使用数据。
    *   简单 :
    *   import globalContext from "../context";
    *   <p>child:{result}</p>
    *   设置到数据的传递:
    *   App.jsx:
    *           const [num,setNum] = useState(100);
    *           <globalContext.Provider>
                    <Child userName={userName}/>
                </globalContext.Provider>
        接收:
            import globalContext from "../context";
            const {num,setNum} = useContext(globalContext);
            <p>child:{num}</p>
    * */
    function App(props) {
        const [userName] = useState("zhangsan");
        const [num,setNum] = useState(100);
        return (
            <div>
                <globalContext.Provider value={{num,setNum}}>
                    <Child userName={userName}/>
                </globalContext.Provider>
    ​
            </div>
        );
    }
    ​
    export default App;
    
  • components->Child.jsx

    import React,{useContext} from 'react';
    import globalContext from "../context";
    import A from "./A";
    import B from "./B";
    ​
    function Child(props) {
        const {num,setNum} = useContext(globalContext);
        return (
            <div>
                <p>child:{num}</p>
                <button onClick={()=>{
                    setNum(num+10)
                }}>child当中修改num</button>
                <hr/>
                <A userName={props.userName}/>
                <hr/>
                <B userName={props.userName}/>
            </div>
        );
    }
    ​
    export default Child;
    
  • components->A.jsx

    import React, {useContext} from 'react';
    import globalContext from "../context"
    function A(props) {
        const {num,setNum} = useContext(globalContext);
        return (
            <div>
                <h3>A</h3>
                <p>{props.userName}</p>
                <p>num:{num}</p>
                <button onClick={()=>setNum(num+3)}>A修改num</button>
            </div>
        );
    }
    ​
    export default A;
    
  • components->B.jsx

    import React, {useContext, useMemo} from 'react';
    import globalContext from "../context";
    // hook函数只允许写在函数组件或hook函数中
    function useMy(){
        return useContext(globalContext);
    }
    function B(props) {
        const {num,setNum} = useMy();
        return (
            <div>
                <h3>B</h3>
                <p>{props.userName}</p>
                <p>{num}</p>
                <button onClick={()=>setNum(num+9)}>B,修改num</button>
            </div>
        );
    }
    ​
    export default B;
    

6.9- useReducer

useReducer是useState的一个替代方案。

useReducer也被称为状态管理器。通过useReducer可以对自身的状态进行集中式管理。

如果你的组件中的数据逻辑关系比较复杂。

import React,{useReducer} from 'react';
/*
reducer的使用流程:
1- 创建一个函数,该函数可以称为reducer函数,作用:可以对数据(数据状态)进行操作
2- 该函数可以接收两个参数,第一个参数是状态,第二个参数是荷载的内容.
3- 需要通过useReducer,可以获得状态以及修改状态的方法
4- useReducer接收两个参数:1-reducer函数 2- 初始状态
5- dispatch 执行时,需要传递一个对象.
* */
// prevState是要更改的状态,action的值为dispatch函数传递的数据(action对象)
// 返回的值是最新的状态.
// action对象需要设置type属性,通过type属性可以得知要对数据进行怎样的操作.
const reducer = function(prevState,action){
    const state = {...prevState};
    // 将状态更改为{num:100}
    if(action.type === "jia"){
        state.num+=action.payload;
    }
    if(action.type === "jian"){
        if(state.num>1)
            state.num-=1;
    }
    return state;
}
function App(props) {
    // console.log(useReducer());// [undefined,f]
    // console.log(useReducer(reducer,{
    //     num:1
    // }));// [ {num: 1},f]
​
    const [state,dispatch] = useReducer(reducer,{num:0});
​
    return (
        <div>
            <button onClick={()=>{
                dispatch({
                    type:"jian"
                });
            }}>-</button>
            {state.num}
            <button onClick={()=>{
                dispatch({
                    type:"jia",
                    payload:2
                });
            }}>+</button>
        </div>
    );
}
​
export default App;

6.10- useEffect(常用)

  • App.jsx
import React, {useEffect, useState} from 'react';
import Child from "./Child";
function App(props) {
    const [num,setNum] = useState(0);
    const [userName,setUserName] = useState("zhangsan");
    const [isShow,setIsShow] = useState(true);
    // 函数体的内容在挂载组件时(UNSAFE_componentWillMoount),更新状态时会执行(UNSAFE_componentWillUpdate)。
    // console.log(document.querySelector("button"));
​
    // 如果以下条件满足,说明组件已经挂载完毕了
    // if(document.querySelector("button"))
    //     console.log(document.querySelector("button").innerText,num)
​
    // 1- useEffect可以接收一个函数(相当于componentDidMount,componentDidUpdate)
    // useEffect(()=>{
    //     console.log("useEffect",document.querySelector("button").innerText);
    // })
​
    // 2- useEffect第二个参数是一个数组,
    //     2-1如果你省略该数组,当任何状态更新都会执行第一个参数(回调函数)
    //     2-2如果第二个参数设置为一个空数组,那么回调函数只会在挂载完毕之后执行一次,更新阶段不会执行。(componentDidMount)
    //     2-3如果数据内指定了状态,那么回调函数只会在指定的状态发生变化时和挂载结束时执行。componentDidMount componentDidUpdate
    // useEffect(()=>{
    //     console.log("useEffect,num",document.querySelector("button").innerText);
    // },[num])
    //
    // useEffect(()=>{
    //     console.log("useEffect,userName",document.querySelector("button").innerText);
    // },[userName])
​
​
​
    return (
        <div>
            <button onClick={()=>setNum(num+1)}>{num}</button>
            <button onClick={()=>setUserName(userName+"!")}>{userName}</button>
            <button onClick={()=>setIsShow(!isShow)}>显示与隐藏</button>
            {
                isShow && <Child></Child>
            }
        </div>
    );
}
​
export default App;
  • Child.jsx
import React, {useEffect} from 'react';
​
function Child(props) {
    // 可以返回一个函数,该函数会在组件销毁前执行
    useEffect(()=>{
        console.log("componentDidMount");
        return function(){
            console.log("componentWillUnmount");
        }
    },[])
    return (
        <div style={{width:200,height:200,border:"1px solid green"}}></div>
    );
}
​
export default Child;

6.12- 函数组件中使用axios

  • 下载axios

    cnpm install axios
    
  • 使用

    import React, {useCallback, useEffect, useState} from 'react';
    import axios from "axios";
    function App(props) {
        const [items,setItems] = useState([]);
        // 如果不写第二个参数,那么useEffect的回调函数会在挂载完毕执行,会在状态更新之后执行。
        // 第二个参数设置为[],可以避免重复发送请求
        // useEffect(()=>{
        //     console.log("useEffect")
        //     axios.get("https://api.github.com/search/repositories",{
        //         params:{
        //             q:"r",
        //             sort:"stars"
        //         }
        //     }).then(({data})=>{
        //         setItems(data.items);
        //     }).catch(err=>{
        //         alert("网络异常")
        //     })
        // },[])
    ​
        const getGit = useCallback(async ()=>{
            try{
                // https://api.github.com/search/repositories?q=r&sort=stars
                const {data} = await axios.get("https://api.github.com/search/repositories",{
                    params:{
                        q:"r",
                        sort:"stars"
                    }
                })
                setItems(data.items);
            }catch (err){
                console.log(err);
                alert("网络异常",err);
            }
        },[])
        // useEffect指定的回调函数不允许为async函数
        useEffect(()=>{
            // 1- useEffect回调函数中创建一个新的函数
            // const getGit = async ()=>{
            //     try{
            //         // https://api.github.com/search/repositories?q=r&sort=stars
            //         const {data} = await axios.get("https://api.github.com/search/repositories",{
            //             params:{
            //                 q:"r",
            //                 sort:"stars"
            //             }
            //         })
            //         setItems(data.items);
            //     }catch (err){
            //         console.log(err);
            //         alert("网络异常",err);
            //     }
            //
            // }
            // getGit();
    ​
            // 2- 可以将async函数放置到外部
            getGit();
    ​
        },[])
        return (
            <div>
                {
                    items.map(item=>(
                        <p key={item.id}><a href={item.html_url}>{item.name}</a></p>
                    ))
                }
            </div>
        );
    }
    ​
    export default App;
    

6.11- useLayoutEffect

import React, {useEffect, useLayoutEffect, useState} from 'react';
​
function App(props) {
    // 声明了状态num,初始值为0
    const [num,setNum] = useState(0);
    // useEffect:不会阻塞浏览器的渲染,会有一个短暂的闪烁,(抖动)
    // useEffect(()=>{
    //     console.log("useEffect",num)
    //     const pre = Date.now();// 获取当前时间
    //     // 延迟0.5秒
    //     while(Date.now()-pre<500){}
    //     if(num === 0)
    //         setNum(Math.random());
    // },[num])
​
    // useLayoutEffect会阻塞浏览器的渲染。
    useLayoutEffect(()=>{
        console.log("useLayoutEffect",num)
        const pre = Date.now();// 获取当前时间
        // 延迟0.5秒
        while(Date.now()-pre<500){}
        if(num === 0)
            setNum(Math.random());
    },[num])
​
    return (
        <div>
            <div onClick={()=>setNum(0)}>{num}</div>
        </div>
    );
}
​
export default App;

6.12- 自定义Hook(项目中讲)