React简介

1、简介

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了

React主要用于构建UI。你可以在React里传递多种类型的参数,如声明代码,帮助你渲染出UI、也可以是静态的HTML DOM元素、也可以传递动态变量、甚至是可交互的应用组件。

React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。

2、与Angular和Vue比较

  • angular
    • 2009年1.x 使用MVC架构,学习曲线比较陡;2.x使用MVVM架构开始出现组件,支持组件和TS
    • ajax交互是限制的,内部包含了$http
  • vue2
    • 使用MVVM架构
    • ajax交互没有限制,但推荐axios、vue-resource等
  • react
    • 更专注于View层
    • 实现大功能需要使用插件

3、特点

  • 优势
    • 声明式设计 −React采用声明范式,可以轻松描述应用。

      //命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。类似jquery 操作DOM,创建一个页面,一点点的告诉DOM 怎么去挂载,你要怎么去做;JQ\原生 都是命令式编程,都是在做DOM 操作。
      
      //声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。
    • 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。

    如下图,当表格需要排序重新渲染时:

    vDom.png

    虚拟DOM(Virtual DOM)用js 对象来模拟页面上的DOM 和DOM嵌套实现DOM 元素的高效更新

    虚拟dom.png

    Diff 算法

    • tree diff
    • component diff
    • element diff

    diff算法.png

    • 灵活 −React可以与已知的库或框架很好地配合。
    • JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
    • 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。模块化

    vue的组件是通过.vue 文件(template/script/style)实现的

    react中没有像vue中那样的模板文件,在react里一切都是以js来实现的

    • 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
  • 劣势
    • 学习成本很高,学习曲线很陡
    • react本身能做的事并不多,想做大东西必须得用react插件(全家桶)

4、如何学习

  • 理解作者思想,接受它的世界观
  • 接受它的中二设定 --- 自定义语法 jsx

    //js - string
    let a = '<div>hello react!</div>'
    
    //jsx
    let a = <div>hello react!</div>

5、书写格式

  • 单个标签的书写格式

    let a = <div>hello react!</div>
  • 多个标签的书写格式:外面必须包裹一层根标签

    let a = <div><div>sadasd</div><span>asdasd</span></div>
  • 可以自由缩进

    let a = <div>
                <div>sadasd</div>
                <span>asdasd</span>
            </div>
  • 允许加括号

    let a = (<div>
                <div>sadasd</div>
                <span>asdasd</span>
            </div>)
  • 单标签规则:必须闭合

    <div></div>
    <img />
    <input/>
    <br/>
    <div></div>
  • 样式类名class:写为className

    <div className='aaa'></div>
  • jsx里面使用js代码 - { }

    var a = 'hello react!';
    let b = <div> { a } </div>
        
    var imgSrc = "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2717595227,1512397782&fm=26&gp=0.jpg";
    <img src={imgSrc}/>
  • 行内样式style:值放到{}里

jsx支持style - 里面使用json

<h1 style={{'color': "red"}}>hello react!</h1>
  • 第一层是使用告诉jsx我要用js了
  • 第二层的是json的

6、JSX实例

JSX需要通过babel.js解析,将其转为JS代码,让浏览器能够识别

babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码,这样就能在目前不支持 ES6 浏览器上执行 React 代码。Babel 内嵌了对 JSX 的支持。在script标签中需要将type类型声明为“text/babel”

<script src="https://unpkg.com/@babel/standalone@7.10.4/babel.min.js"></script>
<script type="text/babel">
    // let vDom = ("hello world");
    
    //创建JSX
    /*@jsx createElement*/  //@jsx是babel的自执行指令
    let vDom = (
            <div id="app" name="myapp">
                hello world!
                <ul>
                    <li>张三</li> 
                    <li>李四</li> 
                </ul>    
            </div>
    )
    
    //将JSX转为虚拟DOM对象,是一个json对象
    function createElement(nodeName, attr, ...args) {
            return {
                nodeName: nodeName,
                attr: attr,
                children: [].concat(...args)
            }
    }

    //将虚拟对象渲染到页面中
    function render(vnode) {
            //如果节点内容是文本
            if(vnode.split) {
                return document.createTextNode(vnode);
            }

            //如果不是文本
            let node = document.createElement(vnode.nodeName);
            //添加属性
            let attr = vnode.attr || {};
            Object.keys(attr).forEach( k => {
                node.setAttribute(k, attr[k]);
            });

            //添加子节点
            (vnode.children || []).forEach(n => {
                node.appendChild(render(n));
            });

            return node;
        }

        let dom = render(vDom);
        document.body.appendChild(dom);
</script>

React开发模式

1、直接引入

  • React可以直接下载使用,下载官网 http://facebook.github.io/react/
  • 可以使用 bower 包管理器

    • js所有的框架库包管理器
    • 依赖于node.js(全局安装配置):

    npm config set prefix "D:Program Filesnodejsnode_global"

    npm config set cache "D:Program Filesnodejsnode_cache"

    • 安装bower: cnpm install bower -g
    • bower info react:查看react框架的信息
    • bower install react:下载react最新版
    • bower install react#版本号:下载react指定版本

react.js:React的核心库

react-dom.js:中提供了函数用来渲染页面

<script src="./bower_components/react/react.development.js"></script>
<script src="./bower_components/react/react-dom.development.js"></script>
<script type="text/javascript">
    //名
    //属性json
    //子节点
    var myp=React.createElement("p",null,"这是一个文段")
    var mydiv=React.createElement("div",{id:"test"},"我都div",myp)
    
    //ReactDOM.render(
    //   组件(元素、内容), 
    //    放到哪
    //);
    ReactDOM.render(mydiv,document.getElementById("app"))
</script>
在html文档中直接使用

使用babel.min.js、react.js、react-dom.js实现在js 中支持JSX 语法,注<script type='text/babel>

babel.min.js 负责-----------转换JSX 为babel 对象{nodename,attr,children}

react.js 中的createElement 负责-----------转换babel 对象为dom 树

react-dom.js 中的render 负责-----------将dom 树渲染添加到指定dom 节点

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>        
        <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
        <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
        <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>    
    </head>
    <body>
        <div id="app"></div>
    </body>
    <script type="text/babel">

        ReactDOM.render(
            <h1>这是React</h1>,    
            document.getElementById('app')
        );
    </script>
</html>

2、npm开发,基于webpack构建

使用create-react-app快速构建开发环境

create-react-app来自于Facebook,通过该命令无需配置就能快速构建React开发环境。

create-react-app自动创建的项目是基于Webpack+ES6。

推荐在C盘以外的盘符创建一个空文件夹,比如定义:ggReact ,然后通过cmd小黑窗定位到我们新建的ggReact文件夹再开始 npm 等一系列操作,操作步骤大概如下:

$ npm config set prefix "D:\Program Files\nodejs\node_global"
$ npm config set cache "D:\Program Files\nodejs\node_cache"
$ cnpm install -g create-react-app  
$ npm cache clean --force
$ npm config set registry https://r.npm.taobao.org
$ create-react-app my-app    /*创建项目*/
$ cd my-app/    /*进入项目目录*/
$ npm start        /*启动项目*/

应用名称不能包含大写字母,不能驼峰式,只能是小写字母

createReact01.png

createReact02.png

createReact03.png

在浏览器中打开http://localhost:3000/ ,显示结果如下:

firstResult.png

React第一个项目

1、React项目目录结构

项目结构.png

(1)node_modules: 这里面包含了react项目中会用到的一些组件,install的时候下载下来的,你可以进去看看,有一些如base64之类的我们可能会用到的组件;

(2)public:里面包含了我们项目中的启动页面,react比较适合单页面项目应用开发,所以暂时只包含一个index.html,并且这也是react工程的入口页面,入口页面怎么理解,就是你启动项目后,打开的首页,第一眼呈现的那个页面;

(3)src:里面包含了一些我们自己使用的js文件,css文件,img文件等等,但你打开src文件夹你会发现很混乱,不要管什么app.js,你就看到index.js即可,系统默认将index.html对准了index.js,index.js也就是我们的入口js,他和index.html所对应。

2、命令与代码说明

  • npm run eject

暴露webpack配置文件,修改webpack.config.js 配置“cheap-module-source-map” 成为“eval-source-map”启动调试。

  • npm run build

生成build文件夹,并将文件挂载到服务器中,以production 模式运行;

修改'source-map'成为“eval-source-map”,并且修改shouldUseSourceMap 值为true,方便在production 开发模式下进行调试。

service worker是在后台运行的一个线程,可以用来处理离线缓存、消息推送、后台自动更新等任务。serviceWorker.js就是为react项目注册了一个service worker,用来做资源的缓存,这样你下次访问时,就可以更快的获取资源。而且因为资源被缓存,所以即使在离线的情况下也可以访问应用(此时使用的资源是之前缓存的资源)。
注意,serviceWorker只在生产环境中生效(process.env.NODE_ENV === ‘production’)

根据package.json 的scripts 配置项可以看到,npm startnode scripts/start.js,它使用了start.js 来启动项目。因此我们找到该文件,找到 DEFAULT_PORT 变量,修改它的默认值为 3333 即可。

3、让目录结构更易懂

接下来,我们改动一下src里面的内容和结构,让首次接触react的小伙伴尽量能觉得他有点亲切的感觉,事实证明,越亲切的事物我们越希望去接近。

src文件夹内容修改如图:

项目结构1.png

src文件夹下原来的所有东西,除了index.js之外,都可以删掉,这样就可以轻装上阵了

接下来,我们做一些代码修改,展示一个含有样式的hello world示例。

项目结构2.png

项目结构3.png

项目结构4.png

项目结构5.png

项目结构6.png

React JSX

1、JSX介绍

全称: JavaScript XML, React 使用 JSX 来替代常规的 JavaScript。

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

我们不需要一定使用 JSX,但它有以下优点:

2、使用 JSX

JSX 看起来类似 HTML ,我们可以看以下实例:

html文件:

  <body>
    <div id="root"></div>
  </body>

js文件:

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

注意:代码中能够嵌套多个 HTML 标签,但需要使用一个标签元素包裹它

ReactDOM.render(
  <h1>这是错误的例子</h1>
  <span>假设这里是标题下面的内容</span>,
  document.getElementById("example")
);
ReactDOM.render(
  <section>
    <h1>这是正确的例子</h1>
    <span>假设这里是标题下面的内容</span>
  </section>,
  document.getElementById("example")
);

3、独立文件

如果需要使用公共样式,可以将React JSX 代码可以放在一个独立文件上,并在不同的js文件中引入即可,如下:

AppTop.js文件中:

import React, {Component} from 'react';
import '../css/demo.css';

//类定义
class AppTop extends Component {
    render() {
        return (
            <header>
                <h1>我是公共标题栏</h1>
            </header>
        );
    }
}

export default AppTop; //将自定义的DOM元素暴露出来,供给其他的页面使用

index.js文件中:

import React from 'react';
import ReactDOM from 'react-dom';
import AppTop from './js/AppTop.js';

ReactDOM.render(
    <AppTop />, 
    document.getElementById('root')
);

4、JavaScript 表达式

我们可以在 JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中。实例如下:

ReactDOM.render(
    <div>
      <h1>{1+1}</h1>
    </div>,
    document.getElementById('example')
);

5、样式

React 推荐使用内联样式。React 会在指定元素数字后自动添加 px

var myStyle = {
    fontSize: 100,  //等价于fontSize: '100px',
    color: '#00ff00'
};
ReactDOM.render(
    <h1 style = {myStyle}>我是标题</h1>,
    document.getElementById('example')
);

6、注释

注释需要写在花括号中,需要注意的是:

ReactDOM.render(
    /*标签外部的注释 */
    <h1>
        我是标题
        {/*标签内部的注释*/}
        {
            //标签内部的注释
        }
    </h1>,
    document.getElementById('example')
);

7、数组

JSX 允许在模板中插入数组,数组会自动展开所有成员

var arr = [
  <h1>HTML</h1>,
  <h2>CSS</h2>
];
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById('example')
);

8、图片

9、条件渲染

在 JSX 内部不能使用 if else 语句,但可以使用 三元运算 表达式来替代。

var i = 10;
ReactDOM.render(
    <div>
      <h1>{i == 1 ? 'True!' : 'False'}</h1>
      {i > 0 ? <div className="box"></div> : '' }  
    </div>,
    document.getElementById('example')
);
var i = 10;
if(i > 0){ //使用if语句的好处是:如果条件不满足且不需要渲染组件时不需要写else
    box = <div className="box"></div>;
}
ReactDOM.render(
    <div>
      { box }
    </div>,
    document.getElementById('example')
);
{i > 0 && <div className="box"></div>}

10、列表渲染

通过数组的map函数

aGoods:[
    {id:1,title:"潮流女装"},
    {id:2,title:"品牌男装"},
    {id:3,title:"手机电脑"}
]

<ul>  
    {   
        this.state.aGoods.map((item,index)=>{   
            return ( //通过return返回多个li元素             
                <li key={index}>{item.title}</li> 
            )        
        })    
    }
</ul>

11、HTML 标签 vs React 组件

React 可以渲染 HTML 标签 (strings) 或 React 组件 (classes)。React JSX 使用大、小写的约定来区分本地组件的类和 HTML 标签。

var my = <h1>我是标题呀</h1>;
ReactDOM.render(my, document.getElementById('example'));
class My extends React.Component {
    render() {
        return (<h1>标题1</h1>);
    }
}

ReactDOM.render(<My />, document.getElementById('example'));

//var my = <My />;
//ReactDOM.render(my, document.getElementById('example'));

React 组件

React.js 中一切皆组件,用 React.js 写的其实就是 React.js 组件。

我们在编写 React.js 组件的时候,一般都需要继承 React.js 的 Component(类定义)。一个组件类必须要实现一个 render 方法,这个 render 方法必须要返回一个 JSX 元素。但这里要注意的是,必须要用一个外层的 JSX 元素把所有内容包裹起来。返回并列多个 JSX 元素是不合法的。

1、定义单个组件

(1)定义组件
import React, {Component} from 'react';
class MyApp extends Component {
}

import React from 'react';
class MyApp extends React.Component {
    constructor(props){
        super(props);
        this.inHTML="<span style='color:red'>文本颜色</span>"
      }
    
    render() {
        return (
            <div>
                <div dangerouslySetInnerHTML={{__html:this.inHTML}}></div>
                <h2>这是标题2</h2>
                <h3>这是标题2</h3>
            </div>
        );
    }
}
export default MyApp;
import React from 'react';
//箭头函数
const MyApp = () => <div><p>这是一个段落</p></div>;
export default MyApp;


//普通函数
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
export default Welcome;
var MyApp = React.createClass({
  render: function() {
    return <h1>Hello World!</h1>;
  }
});
(2)使用组件
import React from 'react';
import ReactDOM from 'react-dom';
import MyApp from './js/MyApp.js'; //导入自定义组件

ReactDOM.render(<MyApp />, document.getElementById('root'));

2、定义复合组件

我们可以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离

import React from 'react';
import ReactDOM from 'react-dom';

class WebSite extends React.Component {
    render() {
        return (
              <div>
                <Name name={this.props.name} />
                <Link site={this.props.site} />
              </div>
        );
    }
}
//局部组件Name
class Name extends React.Component {
    render() {
        return (
              <h1>{this.props.name}</h1>
        );
    }
}
//局部组件Link
class Link extends React.Component {
    render() {
        return (
              <h1>{this.props.site}</h1>
        );
    }
}

ReactDOM.render(
    <WebSite name="百度一下,你就知道" site='http://www.baidu.com' />, 
    document.getElementById('root')
)

3、样式组件

给React组件添加样式,常见的方式有

在React中有css-in-js,它是一种模式,这个css由js生成而不是在外部文件中定义,是CSS Modules,主要是借助第三方库生成随机类名称的方式来建立一种局部类名的方式

这种css-in-js的第三方模块有很多:可以访问:https://github.com/MicheleBertoli/css-in-js

今天的主要学习的是github上star数最多的,styled-components

使用styled-components的好处是:它可以让组件自己的样式对自己生效,不是全局生效,做到互不干扰

首先你得通过npm或者cnpm进行安装styled-components模块

cnpm install -S styed-components

在安装完后,在使用styled-components的文件内,通过import的方式引入该模块。如下代码所示,在文件的上方引入styled-components,实例化了一个styled对象,通过给styled对象下添加你想要的html元素

style.js:

import styled from "styled-components"; // 引入styled-components库,实例化styled对象

// 声明样式ButtonA组件,通过styled对象进行创建,注意styled.html元素,后面是反引号
const ButtonA = styled.button`
  width: 100px;
  height: 40px;
  border-radius: 3px;
  outline: none;
  border: none;
  cursor: pointer;
  background: #abcdef;
  color: #fff;
`;

// 声明样式ButtonB组件,同时样式ButtonB组件继承了ButtonA组件的样式,又给自身拓展了样式,更改了自身的背景色
const ButtonB = styled(ButtonA)`
  background: red;
  font-size:${(props) => props.myfont ? props.myfont: '18px'};
`;//样式组件可以接收props

// 对外暴露出去
export {
    ButtonA,
    ButtonB
}

Header.js:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
  ButtonA,
  ButtonB
}
from './style';

export default class Header extends Component {
  render(){
    return (
      <Fragment>
             <ButtonA>按钮A</ButtonA>
             <ButtonB myfont="24px">按钮B</ButtonB>
      </Fragment>
    );
  }
}

更多使用情形可参考:https://mp.weixin.qq.com/s/2fMCStpGmbhaQGJN-aADHQ

React Props

React 的一大特点是单向数据流。React 中的每一个组件,都包含有一个属性(props),属性主要是从父组件传递给子组件的,在组件内部,我们可以通过this.props获取属性对象。

1、定义和使用props 传值

  • 通过React类定义组件时:

    • 在父组件render 方法中调用组件时使用key/value 的形式来指定属性。
    • 在自定义子组件中通过this.props.key 来获得组件属性的值,需要使用{}括起来。
// 类定义组件时,使用属性 this.props.属性名称
class MyApp extends React.Component {
    render() {
        return (<p>{this.props.name}</p>);
    }
}

ReactDOM.render(
    <MyApp name="张三" />, 
    document.getElementById('root')
);
  • 通过React函数定义 组件时:

    • 在父组件render 方法中调用组件时使用key/value 的形式来指定属性。
    • 在自定义子组件中通过函数接收参数propsprops.key来获得组件属性的值,需要使用{}括起来
// 函数定义组件时,在组件内部使用属性值:props.属性名称
function Welcome(props) { // 函数需要传递一个参数props
    return(<h2>{props.title}</h2>)
}

ReactDOM.render(
    <Welcome title="张三" />, 
    document.getElementById('root')
);

练习:定义多层嵌套组件(<Logo>和<Child>)实现react 官网头部<MyHeader>

2、默认Props

  • 了解:在react中可以定义默认props,使用es5时,可以使用getDefaultProps:
var MyApp = React.createClass({
  getDefaultProps: function() {
    return {
      name: 'demo'
    };
  },
  render: function() {
    return <h1>This is my {this.props.name}</h1>;
  }
});

ReactDOM.render(
  <MyApp />,
  document.getElementById('demo')
);
  • 了解:es6中使用static defaultProps
class MyApp extends React.Component {
    //如果babel设置为es6的转码方式,会报错,因为定义静态属性不属于es6,而在es7的草案中。ES6的class中只有静态方法,没有静态属性。
    static defaultProps = {
        name: 'demo'
    }
    render() {
        return <p>this is my {this.props.name}</p>
    }    
}

ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);
  • 掌握使用:es6定义默认props的正确方式
class MyApp extends React.Component {
    render() {
        return <p>this is my {this.props.name}</p>
    }    
}
//由于是用ES6 class语法创建组件,其内部只允许定义方法,而不能定义属性,class的属性只能定义在class之外。所以defaultProps要写在组件外部。
MyApp.defaultProps = {
    name: 'xxx'
};

ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

3、多属性传值

(1)定义一个this.props对象,在对象中声明多个键值对,用于表示组件的属性

(2)在组件中使用{...this.props}的方式传递属性。“...”表示JSX的延展操作符,这种方式可以很方便的为组件指定多个属性,并且为属性的值指定数据类型。

class MyApp extends React.Component {
    render() {
        return(
            <h1>{this.props.name} : {this.props.age} : {this.props.sex}</h1>
        );
    }
}
MyApp.props = {
    name: '张三', 
    age: 18,
    sex: '男'
};

ReactDOM.render(
    //<MyApp name='张三' age='18' sex='男' />
    <MyApp {...MyApp.props}/>,
    document.getElementById('root')
);

练习:渲染评论列表

分析示例结构,使用不同方式创建组件:

state-props.png

数据:

commentList:[
    {id:1,user:"张三",content:"哈哈,沙发"},
    {id:2,user:"李四",content:"哈哈,板凳"},
    {id:3,user:"王五",content:"哈哈,凉席"},
    {id:4,user:"赵六",content:"哈哈,砖头"},
    {id:5,user:"田七",content:"哈哈,楼下"}
]

4、Props验证类型

Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用。

了解:React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

var title = 'abc';
// var title = 123;
class MyApp extends React.Component {
    propTypes: {
        title: React.PropTypes.string.isRequired
    }
    render() {
        return (<h1>{this.props.title}</h1>);
    }
}

ReactDOM.render(
    <MyApp title={title}/>, 
    document.getElementById('root')
);

掌握使用:从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,我们建议使用 prop-types库来定义PropTypes。

import PropTypes from 'prop-types';

class MyApp extends React.Component {
    render() {
        return (
            <h1>{this.props.name}</h1>
        );
    }
}
MyApp.propTypes = {
    name: PropTypes.string
}

ReactDOM.render(
    <MyApp name='123'/>,
    document.getElementById('root')
)

更多的验证器如下:

MyApp.propTypes = {
    // 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
    optionalArray: PropTypes.array,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    // 可以被渲染的对象 numbers, strings, elements 或 array
    
    optionalNode: PropTypes.node,
    //  React 元素
    optionalElement: PropTypes.element,
 
    // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
    optionalMessage: PropTypes.instanceOf(Message),
 
    // 用 enum 来限制 prop 只接受指定的值。
    optionalEnum: PropTypes.oneOf(['News', 'Photos']),
 
    // 可以是多个对象类型中的一个
    optionalUnion: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.instanceOf(Message)
    ]),
 
    // 指定类型组成的数组
    optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
 
    // 指定类型的属性构成的对象
    optionalObjectOf: PropTypes.objectOf(PropTypes.number),
 
    // 特定 shape 参数的对象
    optionalObjectWithShape: PropTypes.shape({
      color: PropTypes.string,
      fontSize: PropTypes.number
    }),
 
    // 任意类型加上 `isRequired` 来使 prop 不可空。
    requiredFunc: PropTypes.func.isRequired,
 
    // 不可空的任意类型
    requiredAny: PropTypes.any.isRequired,
 
    // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
    customProp: function(props, propName, componentName) {
      if (!/matchme/.test(props[propName])) {
        return new Error('Validation failed!');
      }
    } 
}

练习:多级嵌套组件之间,从父到多级后代的传值。

5、props 与组件嵌套

在父组件上render 渲染中,使用this.props.children 即可实现组件之间的嵌套:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Tab extends Component {
  render() {
    return (
      <ul>
        {this.props.children}
      </ul>
    )
  }
}

class TabItem extends Component {
  render() {
    return (
      <li>
        {this.props.name}
      </li>
    )
  }
}

export default class Area extends Component {
  render() {
    return (
      <Tab>
        <TabItem name="上海" />)}
      </Tab>
    )
  }
}

React State

React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。

1、如何定义State

定义一个合适的State,是正确创建组件的第一步。State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。

组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:

  1. 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
  2. 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  3. 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  4. 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性。

请务必牢记,并不是组件中用到的所有变量都是组件的状态!

class LikeButton extends React.Component {
    //声明一个状态liked   使用this.state.liked获取状态的值
    getInitialState() {
        return {liked: false};
    }    
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    render() {
        return (
            <button onClick={this.handleClick}>
                你<strong>{this.state.liked ? '喜欢' : '不喜欢'}</strong>我,点我切换
            </button>
        )
    }
}

ReactDOM.render(
    <LikeButton />,
    document.getElementById('root')
)

注意:React在ES6的实现中,规定state在constructor中实现

正确定义State的方式如下:

(1)在constructor中实现state

(2)在constructor中通过bind绑定事件函数(事件函数是用来改变状态)

(3)在事件函数内部使用setState函数更改状态

(4)在组件中的render函数中使用该状态

(5)在组件上需要设置监听事件,去触发事件函数的执行

//定义组件
class LikeButton extends React.Component {
    //constructor表示构造器,在constructor需要声明状态state,在声明state之前需要使用super(props);
    constructor(props) {
        super(props);//使用父类的属性
        
        //声明状态
        this.state = {
            liked: false
        }
        //Currently, you are calling bind. 
        //But bind returns a bound function. 
        //You need to set the function to its bound value.
        //目前,你正在调用绑定。但是绑定返回绑定函数。您需要将函数设置为其绑定值。
        this.handleClick = this.handleClick.bind(this);
    }
    
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    
    render() {
        return (
            <button onClick={this.handleClick}>
                你<strong>{this.state.liked ? '喜欢' : '不喜欢'}</strong>我,点我切换
            </button>
        )
    }
}

ReactDOM.render(
    <LikeButton />,
    document.getElementById('root')
)

2、setState设置状态

(1)语法:

setState(object nextState[, function callback])

setState((prevState)=>nextState[, function callback])

(2)说明:

  • setState是React事件处理函数中和回调函数中触发UI更新的主要方法。
  • 不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换。
  • setState()不一定是同步的,为了性能提升,React会批量执行state和DOM渲染。
  • setState()总是会触发一次组件重绘,但可在shouldComponentUpdate()中实现一些条件渲染逻辑来解决。
class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0
        };

        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(event) {
        this.setState({clickCount: this.state.clickCount + 1});
    }

    render() {
        return (
            <h2 onClick={this.handleClick}>
                点击后次数变更: {this.state.clickCount}
            </h2>
        );
    }
}

ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

设置多个状态时,可以在setState函数的第二个参数可以传递一个function回调函数,如下:

class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0, 
            isRed: false,
            smallFont: true
        };

        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(event) {
        this.setState(
            {clickCount: this.state.clickCount + 1},
            function() {
                this.setState(
                    {isRed: !this.state.isRed}, 
                    function() {
                        this.setState({smallFont: !this.state.smallFont});
                    }
                );
            }
            
        );
        console.log(this.state.isred);
    }

    render() {
        var redStyle = {color: 'red', fontSize: 50};
        var blueStyle = {color: 'blue', fontSize: 14};
        return (
            <h2 onClick={this.handleClick} style={this.state.isRed ? redStyle : blueStyle}>
                点击后次数变更: {this.state.clickCount}
            </h2>
        );
    }
}

ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

状态上移:当存在多个组件共同依赖一个状态,或是当子组件的props 数据需要被修改时,将这个状态放到对应组件的父组件中:

//子组件
class Site extends React.Component {
    render() {
        return (
            <div>
                <button onClick={this.props.updateState}>点击改变</button>
                <h2>{this.props.myData}</h2>
            </div>
        );
    }
}

//父组件
class Content extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'hello'
        };
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({value: '你好'});
    }

    render() {
        return(
            <div>
                <Site myData={this.state.value} updateState={this.handleChange}/>
            </div>
        );
    }
}

练习:1、倒计时

​ 2、淘宝轮播图

​ 3、图片轮播与走马灯为兄弟组件时做“状态上移”

3、State 与 Props 区别

除了State, 组件的Props也是和组件的UI有关的。他们之间的主要区别是:

  • props 中的数据都是外界传递过来的
  • state 中的数据都是组件私有的;(通过Ajax 获取回来的数据,一般都是私有数据)
  • props 中的数据都是只读的,不能重新赋值
  • state 中的数据都是可读可写的
  • State定义在constructor内部,在super(props)代码后面;Props默认值定义在类(组件)的外部

当子组件的属性值是可变值时,采用状态上移:

状态上移通过属性将<u>父组件的状态</u>传递到子组件,那么父组件的状态发生变化时,<u>子组件的属性</u>也会改变

组件复用.png

React官网图片:

https://reactjs.org.cn/asset/img/hero_bg.png
https://reactjs.org.cn/asset/img/home/1.svg
https://reactjs.org.cn/asset/img/home/2.svg
https://reactjs.org.cn/asset/img/home/3.svg
https://reactjs.org.cn/asset/img/logo.svg
https://reactjs.org.cn/asset/img/qq-ico.png

4、state/props 实现父子组件通信

  • 子组件获取父组件整个组件进行传参

    • 父组件在调用子组件时,传入一整个组件给子组件<Children parent={ this } />
    • 父组件中定义一个方法getChildrenMsg(resulet, msg),用来获取子组件传来的值以及执行其他操作
    • 子组件在通过this.props来获取到一整个组件this.props.parent 或者this.props[parent]
    • 子组件调用父组件步骤2里定义的方法进行传值this.props.parent.getChildrenMsg(this,val)

Parent:

import Children from './Children'
export default class Parent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            msg: '父组件传值给子组件',
            childrenMsg: ''
        }
    }

    getChildrenMsg = (result, msg) => {
        // console.log(result, msg)
        this.setState({
            childrenMsg: msg
        })
    }

    render() {
        return (
            <div>
                <h2>我是父组件 </h2>
                <h3>子组件传来的值为:{ this.state.childrenMsg }</h3>
                <hr/>
                <Children parent={ this } />
            </div>
        )
    }
}

Children:

export default class Children extends Component {
    constructor(props) {
        super(props)
        this.state = {
            msg: '子组件传值给父组件'
        }
    }

    toParent = () => {
        // console.log(this.props.parent.getChildrenMsg.bind(this, this.state.msg))
        this.props.parent.getChildrenMsg(this, this.state.msg)
    }

    render() {
        return (
            <div>
                <h2>{ 我是子组件 }</h2>
                <button onClick={ this.toParent }>子组件传入给父组件</button>
            </div>
        )
    }
}

5、ref 实现父子组件通信

  • 父组件在调用子组件时,通过ref 属性,拿到整个子组件<Children ref='children'>
  • 父组件中通过this.refs.children或者this.refs[children]获取到一整个子组件实例(注意,要在DOM加载后才能获取)
  • ref 用在HTML 元素上时,current 表示该DOM 元素;ref 用在类元素上时,current 表示该组件的实例对象

Parent:

constructor(props){
    super(props);
    this.state={
        childrenMsg:""
    }
    this.myref=React.createRef();//ref推荐使用方式之——1、创建
}

getChildrenMsg = () => {
    console.log(this.myref.current)//ref推荐使用方式之——3、访问
    this.setState({
        childrenMsg: this.refs['children'].state.msg
    })
}

render() {
    return (
        <div>
            <button onClick={ this.getChildrenMsg }>获取/更新子组件的值</button>
            <h3>子组件传来的值为:{ this.state.childrenMsg }</h3>
            {/*ref推荐使用方式之——2、使用*/}
            {/*注:ref不能使用在函数子组件上 */}
            <h3 ref={this.myref}>我要引入子组件了:</h3>   
            <hr/>
            {/*注:ref不能使用在函数子组件上 */}
            <Children ref="children" />
        </div>
    )
}

注意:ref从一定程度上增加了组件之间的耦合性,导致难以分离,所以如果可以用props来处理的事情,尽量不要用ref来处理

适合使用refs 的情况:

  • 管理焦点,文本选择或媒体播放
  • 触发强制动画
  • 集成第三方DOM 库

避免使用refs 来做任何可以通过声明式实现来完成的事情。

React组件生命周期

1、diff算法.png

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

lifecircle.png

  • 组件创建阶段:一辈子只执行一次

    componentWillMount:
    render:
    componentDidMount:
    
  • 组件运行阶段:按需,根据props 属性或state 状态的改变,有选择性的执行0 到多次

    componentWillReceiveProps:
    shouldComponentUpdate:
    componentWillUpdate:
    render:
    componentDidUpdate:
    
  • 组件销毁阶段:一辈子只执行一次

2、生命周期钩子详解

定义:在特定的阶段,ne你刚刚自动执行的函数(方法)。

  • componentWillMount :在渲染前调用,在客户端也在服务端。
  • componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
  • componentWillReceiveProps :在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
  • shouldComponentUpdate :返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
  • componentWillUpdate:在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
  • componentDidUpdate :在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount: 在组件从 DOM 中移除的时候立刻被调用。
class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            date: new Date()
        };
    }
    //通过componentDidMount 方法设置一个定时器,每隔1秒重新设置时间,并重新渲染:
    componentDidMount() {
        var oThis=this;
        clearInterval(this.timer);

        this.timer=setInterval(function() {
            oThis.setState({
                date: new Date()
            })
        }, 1000)
    }
    
    render(){
        return (
            <h2>{this.state.date.toLocaleTimeString()}</h2>
        );
    }
}

父组件的状态传递到子组件的属性中

class Content extends React.Component {
    //在渲染前调用,在客户端也在服务端
      componentWillMount() {
          console.log('Component WILL MOUNT!')
      }
    //在第一次渲染后调用,只在客户端
      componentDidMount() {
       console.log('Component DID MOUNT!')
      }
    //在组件接收到一个新的 prop (更新后)时被调用
     componentWillReceiveProps(newProps) {
        console.log('Component WILL RECEIVE PROPS!')
      }
    //在组件接收到新的props或者state时被调用
      shouldComponentUpdate(newProps, newState) {
        return true;
      }
    //在组件接收到新的props或者state但还没有render时被调用
      componentWillUpdate(nextProps, nextState) {
        console.log('Component WILL UPDATE!');
      }
    //在组件完成更新后立即调用
      componentDidUpdate(prevProps, prevState) {
        console.log('Component DID UPDATE!')
      }
    //在组件从 DOM 中移除的时候立刻被调用
      componentWillUnmount() {
         console.log('Component WILL UNMOUNT!')
     }
  
      render() {
          return (
              <div>
                  <h3>{this.props.myNumber}</h3>
              </div>
          );
      }
}

class MyApp extends React.Component {
    constructor(props) {
        super(props);
        //声明状态
        this.state = {
            data: 0,
            isRender: true
        };
        
        this.setNewNumber = this.setNewNumber.bind(this);
        this.deleteDOM = this.deleteDOM.bind(this);
    }
    //改变状态值并传递到子组件
    setNewNumber() {
        this.setState({data: this.state.data + 1});
    }
    //删除子组件
    deleteDOM() {
        this.setState({isRender: false})
    }
    
    render() {
        return (
            <div>
                <button onClick={this.setNewNumber}>点我改变状态</button>
                <button onClick={this.deleteDOM}>点我删除子组件</button>
                { this.state.isRender ? <Content myNumber={this.state.data} /> : null }
            </div>
        );
    }
}

ReactDOM.render(
    <div>
        <MyApp />
    </div>,
    document.getElementById('root')
);

React表单与事件

1、React表单组件

在HTML中,表单元素与其他元素最大的不同是它自带值或数据,而且在我们的应用中,只要是有表单出现的地方,就会有用户输入,就会有表单事件触发,就会涉及的数据处理。

在我们用React开发应用时,为了更好地管理应用中的数据,响应用户的输入,编写组件的时候呢,我们就会运用到受控组件与非受控组件这两个概念。

React推荐我们在绝大多数情况下都使用受控组件。这样可以保证表单的数据在组件的state管理之下,而不是各自独立保有各自的数据。

(1)受控组件与非受控组件

大致可分为两类:

  • 受控组件:一般涉及到表单元素时我们才会使用这种分类方法。受控组件的值由props或state传入,用户在元素上交互或输入内容会引起应用state的改变。在state改变之后重新渲染组件,我们才能在页面中看到元素中值的变化,假如组件没有绑定事件处理函数改变state,用户的输入是不会起到任何效果的,这也就是“受控”的含义所在。
  • 非受控组件:类似于传统的DOM表单控件,用户输入不会直接引起应用state的变化,我们也不会直接为非受控组件传入值。想要获取非受控组件,我们需要使用一个特殊的ref属性,同样也可以使用defaultValue属性来为其指定一次性的默认值。

受控组件简单来说就是它的值由React进行管理,而非受控组件的值则由原生DOM进行管理。示例如下:

//受控组件(省略部分代码)
<input type='text' value={this.state.value} onChange={this.handleChange}/>

handleChange(event) {
    this.setState({value: event.target.value});
}


//非受控组件
<input type="text" defaultValue="hello!"/>
(2)更多受控组件

在组件中声明表单元素(input_type/checked textarea select ……)时,一般都要为表单元素传入应用状态中的值,我们需要根据表单元素中用户的输入,对应用数据进行相应的操作和改变。

class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            gender: ''
        };
        this.handleGender = this.handleGender.bind(this);
    }
    handleGender(event) {
        this.setState({gender: event.target.value});
    }

    render() {
        return (
            <div>
                <h2>input_checked 受控组件</h2>
                <input type='radio' value="man" checked={this.state.gender=='man'} onChange={this.handleGender}/>男<br />
                <input type='radio' value="woman" checked={this.state.gender=='woman'} onChange={this.handleGender}/>女<br />
                <div>{this.state.gender}</div>
                
            </div>
        );
    }
}

ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

练习:

​ 1、使用表单组件练习--->父子组件状态上移

​ 2、兄弟组件传参

2、React事件

(1)事件类型

使用React元素处理事件与处理DOM元素上的事件非常相似。不过有一些语法上的差异:

  • React事件使用小驼峰命名法,而不是全部小写命名。
  • React事件使用JSX传递一个函数作为事件处理程序,而不是一个字符串。

示例:

  • 鼠标事件:onClick onDoubleClick onMouseDown
  • 触摸事件:onTouchStart onTouchMove onTouchEnd
  • 键盘事件:onKeyDown
  • 剪切事件:onCopy onCut onPaste
  • 表单事件:onChange onInput onSubmit
  • 焦点事件:onFocus
  • UI事件:onScroll
  • 滚动事件:onWheel
(2)事件对象
  • React对原生的事件系统也进行了封装,在React中的事件对象实际上是一个跨浏览器的虚拟事件对象 ,它拥有和原生事件对象相同的属性和方法。
  • 在react 中使用“return false” 无法阻止默认事件,只能通过事件对象调用“event.preventDefault() ”进行阻止
class ClickEvent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'hello world'
        };
        this.changeData = this.changeData.bind(this);
    }

    changeData(event) {
        console.log(event)
        this.setState({value: '萨瓦迪卡'});
    }

    render() {
        return (
            <div>
                <button onClick={this.changeData}>点击改变</button>
                <h2>{this.state.value}</h2>
            </div>
        );
    }
}
(3)事件绑定

事件处理函数内部使用this 关键词时其指向需要注意:

class ClickEvent extends React.Component {

    changeData() {
        console.log(this);//undefined
    }

    render() {
        return (
            <div>
                <button onClick={this.changeData}>点击改变</button>
            </div>
        );
    }
}

解决方法:

  • constructor 内部对事件处理函数bind 绑定this(官方推荐)
  • 每次事件绑定都对事件处理函数做bind 绑定
  • 定义事件处理函数时使用箭头函数

    <button onClick={e=>this.changeData.call(this)}>点击改变</button>

当事件处理函数需要传参时:

<button onClick={this.changeData.bind(this,id)}>点击改变</button>
<h2>{e=>this.changeData.call(id,event)}</h2>
(4)事件节流和防抖

重绘、重排:html+css

事件处理函数节流

每隔一段时间创建一个定时器。

定义:节约(减少)触发事件处理函数的频率,连续每隔一定的时间执行函数,它是优化高频率执行一段代码的一种手段。

特点:不管事件触发有多频繁,都会保证在规定的间隔时间内真正执行一次事件处理函数。

原理:通过判断是否达到一定的时间来触发函数,若没有规定时间则使用计时器进行延迟,而下一次时间则会重新设定时器,它是间隔执行。

应用场景:常用于鼠标连续多次点击click 事件,鼠标移动mousemove,拖拽,窗口尺寸改动(resize),鼠标滚轮页面上拉(onSroll),上拉刷新懒加载。

以生活中节约用水(三峡大坝设置的多个水闸)为例子:

高频时间就像是一个打开的是水龙头,水流源源不断的大量流出,就像代码在不断地执行,若不加以控制,就会造成资源的一种浪费

对应页面中的,若是表单中连续点击提交按钮,监听滚动事件,连续下拉加载等请求服务器的资源

要节流,控制水流,要他的流水频率降低,每隔一段事件滴一滴水,从而节省资源

在代码中的体现就是:设置一个定时器,让核心功能代码间隔一段时间再去执行。

//适合节流等待duration 时间短的节流方式
<button onClike={  this.throttle1(handleClick,30000)  }>实现节流<button>
    
function throttle1(method,duration){
    var timer=null;
    var preTime=new Date();//绑定渲染的那一刻的时间
    
    return function(e){
        var that=this,
            curentTime=new Date(),//点击那一刻的当前时间
            resTime=currentTime-preTime;
        console.log("时间差",resTime);
        if(resTime<duration){
            clearTimeout(timer);
            timer=setTimeout(function(){
                prevTime=currentTime;//设置为上一次点击的时间
                method.apply(that);
            },duration)
        }else{
            prevTime=currentTime;//设置为当前点击的时间
            method.apply(that);//立即触发handleClick
        }
        
    }
}

//适合节流等待duration 时间长的节流方式
function throttle2(mathod,duration){
    var runFlag=false;
    return function(e){
       if(runFlag){
           return ;
       }else{
           runFlag=true;
           setTimeout(function(){
               method(e);
               runFlag=false;
           },duration)
       }
       
    }
}

事件处理函数防抖

在间隔时间内清除掉其他所有定时器。

定义:防止抖动,防止重复的触发,频繁操作,核心在于,延迟事件处理函数的执行,一定时间间隔内只执行最后一次操作,例如:表单多次提交,推荐使用防抖

原理:它是维护一个计时器,规定在durations时间后触发事件处理函数,但是在duration时间内再次出现变化,就会清除当前的timer 重新计时,这样一来,只有最后一次触发事件处理函数才被真正的触发。

应用场景:输入框效果——每当输入框输入后,键盘弹起时,执行事件处理函数,而不应该是键入内容时触发一次事件处理函数。

同理,搜索引擎,表单联想查询功能时,不是根据用户键入的字母、数字、内容同时进行ajax数据请求的,而是当用户停止输入的时候采取触发查询请求的,这个时候就是用到函数防抖了的。

表单的多次提交,百度搜索等都是用的防抖实现的。
function debounce(method,duration){
    var timer=null;
    return function(e){
        //判断当前是否存在已创建的定时器
        clearTimeout(timer);
        timer=setTimeout(function(){
            method.apply(that)
        },duration)
    }
}

共同点:都是解决频繁操作触发事件处理函数,引起页面卡顿,不流畅等性能问题,都是通过设置延时计时器逻辑来提升性能,以减少http请求次数、节约请求资源。

不同点:函数节流,间隔时间内执行事件处理函数;而函数防抖,一定时间内只执行最后一次操作。

在react 中,使用自定义的节流和防抖:

class eventThrottl extends React.Component {

    constructor(props){
        super(props);
        this.state={
            num:0
        }
        this.handleClik=this.handleClik.bind(this)
        this.handleClickThrottled=this.throttle(this.handleClik,1000)
    }
    //自定义实现throttle 方法
    handleClik(){
        var num=this.state.num
        this.setState({
            num:++num
        })
    }
    render() {
        return (
            <div>
                <button onClick={this.handleClickThrottled}>load More</button>
                <p>按钮被点击了{this.state.num}次</p>
            </div>
        );
    }
}

在react 中,借用第三方库lodash 实现节流和防抖:

//第一步:下载
cnpm i -S lodash.throttle
cnpm i -S lodash.debounce

//第二步:组件内引入
import throttle from "lodash.throttle"
import debounce from "lodash.debounce"

//第三步:组件内使用
constructor(props){
    super(props);
    this.state={
        num:0
    }
    this.handleClik=this.handleClik.bind(this)
    //实现节流
    this.handleClickThrottled=throttle(this.handleClik,1000)
    //实现防抖:表单验证手机号
    this.isPhoneLegal=debounce(this.debounce,1000)
}
(5)react 中使用自定义事件

利用nodejs 自带的events 库中的EventEmitter 方法创建事件对象,也可实现如同vue 中使用自定义事件达到子向父传值的目的。

vue子相父:

parent.vue

​ <child @myevent="handleClik"><child>

​ methods:{

​ handleClik(val){ console.log("你好~");console.log("子组件哪个元素发生了事件:"+val.target)}

​ }

child.vue

​ methods:{

​ changeData(ev){

​ this.$emit("myevent",ev)

​ }

​ }

  • 父组件:List.js

    • 在父组件中引入nodejs 自带的events 库,并使用EventEmitter 创建事件对象;
    • 通过事件对象在componentDidMount 中进行事件绑定;
    • 通过属性传参方式将事件对象传递给子组件;
    • 在componentWillUnmount 中取消事件绑定。
import React from 'react';
import ListItem from "./ListItem";

var EventEmitter = require('events').EventEmitter;
let emitter = new EventEmitter();

class List extends React.Component{
    constructor(props){
        super(props);

        this.state = {
            userin:"请用户输入搜索词:"
        };
    }
    componentDidMount(){
         emitter.addListener('ItemChange', (msg) =>{
            this.setState({
                userin:msg
            })
        }); 
    }
    componentWillUnmount() {
        
    }
    render(){
        return (
            <div>
                <ListItem userin={this.state.userin} myemitter={emitter} />
                
                <h2>儿子节点的搜索词是:</h2>
                <div>
                    {this.state.userin}
                </div>
            </div>
        );
    }
}

export default List;
  • 子组件:ListItem.js

    • 通过this.props 访问到事件对象
    • 在需要传参时通过事件对象的emit 方法,触发父组件中定义的事件处理函数并传参
import React from 'react';
class ListItem extends React.Component {
    handleIn(e){
        this.props.myemitter.emit('ItemChange',e.target.value);
    }
    render(h) {
        return ( 
            <div >
                <input type="text" value={this.props.userin} onChange={this.handleIn.bind(this)}/>
            </div>
        );
    }
}
export default ListItem;

但由于react 的props 既可以专用于父子组件传参,可以传递包括事件处理函数在内的任何类型的值;再使用事件对象进行传参反而复杂了。所以这种方法较少使用,此处仅做了解介绍。

发起XHR 网络请求

在React 中请求数据的几种方式:

  • axios(普遍常用--import axios from "axios";)
  • fetch方法(尝鲜,显逼格用)
  • jquery Ajax(不推荐使用)
  • request(常用,仅次于axios 使用频率)

注意:都是放在componentDidMount 函数中进行数据请求的

原因:

​ a. 生命周期方法的调用是有次序的,由上而下,也就是当说如果你要获取外部数据并加载到组件上,只能在组件"已经"挂载到真实的网页上才能作这事情,其它情况你是加载不到组件的。

​ b. componentDidMount 方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用setState方法,会触发重渲染。

简单示例:

//预先安装:cnpm install axios -S

//引入使用
import axios from "axios";
componentDidMount(){
  axios.get("http://localhost:8000/")
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.error(err); 
  })
}

HOOK

(1)无状态组件和有状态组件

  • 无状态组件(展示组件,函数式组件):就是一个函数没有state,没有生命周期,就是一个简单的视图函数,没有业务逻辑更纯粹的展示UI。

    function NullStatus(){
        return (
              <div>无状态组件</div>
        )
    }
  • 有状态组件(容器组件,类组件):标准使用模式,此class 继承了React里面的组件和props,可以使用生命周期可以在里面写业务逻辑,可以在里面做任何事情。

    class MyApp extends React.Component{  
        constructor(){        
            super();        
            this.state={            
                name:"张三"        
            }    
        };    
        render(){        
            return (            
                <div>                  
                    有状态组件 {this.state.name}           
                </div>              
            )    
        }
    }
  • 什么情况下使用有状态组件?什么情况下使用无状态组件?

    • 如果一个组件需要有自己的私有数据,则推荐使用:class创建的有状态组件
    • 如果一个组件不需要有私有的数据,则推荐使用:无状态组件
    • React 官方说:无状态组件,由于没有自己的state 和生命周期函数,所以运行效率会比有状态组件稍微高一些
  • 有状态组件与无状态组件之间的本质区别就是:有无state 属性 和有无生命周期函数

(2)Hook简介

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

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。

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

Hook 不能在 class 组件中使用

React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件

(3)State Hook

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0);
 

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

解析:

  • useState()就是一个Hook
  • 通过在函数组件里调用useState来给组件添加一些内部 state。
  • React 会在重复渲染时保留这个 state。
  • useState 会返回一对值:当前状态一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。类似于class中的setState()。
  • useState 唯一的参数就是初始 state变量。
  • 有别于class中的this.state,在Hook中,state不一定是一个对象,可以是数值、字符串、对象

(4)class 与function 状态对比

a. 声明state变量
  • 在 class 中,我们通过在构造函数中设置 this.state{ count: 0 } 来初始化 count state 为 0

    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
  • 在函数组件中,没有 this,所以不能分配或读取 this.state。我们直接在组件中调用 useState Hook:

    import React, { useState } from 'react';
    
    function Example() {
      // 声明一个叫 “count” 的 state 变量
      const [count, setCount] = useState(0);
    }
    • 调用 useState 方法的时候做了什么?

    它定义一个 “state 变量”。我们的变量叫 count, 但是我们可以叫他任何名字

    • useState需要哪些参数

    唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,也可以是对象

    • useState方法的返回值是什么?

    当前 state 以及更新 state 的函数,示例中count表示状态,setCount表示更新状态的函数

b. 读取state
  • 在 class 中读取count,使用 this.state.count

    <p>You clicked {this.state.count} times</p>
  • 在函数中,我们可以直接用 count:

    <p>You clicked {count} times</p>
c. 更新state
  • 在 class 中,我们需要调用 this.setState() 来更新 count 值:

    <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        Click me
    </button>
  • 在函数中,我们已经有了 setCountcount 变量,所以我们不需要 this:

    <button onClick={() => setCount(count + 1)}>
        Click me
    </button>

(5)声明多个state变量

function ExampleWithManyStates() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [stu, setStu] = useState({id: 1, name: "小明", sex: "男"});
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  
    
  // ...请补充return jsx中使用state 代码
}

数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字。

(6)Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作(类比于生命周期钩子)。

有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。

  • class组件实现副作用:

    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
      }
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      render() {
        return (
          <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
              Click me
            </button>
          </div>
        );
      }
    }
  • 函数组件实现副作用——useEffect

    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }

(7)Hook规则

  • 只在 React 函数中调用 Hook

    • 在 React 的函数组件中调用 Hook,不要在class组件内部使用hook
    • 在自定义 Hook 中调用其他 Hook(自行学习了解)
    • 只在 React 函数最顶层使用 Hook

不要在循环,条件或嵌套函数(事件处理函数)中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。

路由

vue router

var routes=[{

​ path:"/index",

​ component:()=>require("@/……/index.vue")

},{

​ path:"/detail",

​ component:()=>require("@/……/detail.vue")

}]

var router=new Router(routes)

export default router;

(1)简介

React构建的是单页面应用,使用路由实现页面跳转。通过管理 URL,实现组件的切换和状态的变化

官网:https://reacttraining.com/react-router

  • react-router: 实现了路由的核心功能,提供了router的核心api。如Router、Route、Switch等,但没有提供有关dom操作进行路由跳转的api;
  • react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签。路由模式分为:BrowserRouter(history)(不带#号)和HashRouter(带#号)。原理--history使用pushState和popstate事件构建路由;hash使用window.location.hash和hashchange事件构建路由。可以通过dom操作触发事件控制路由。

(2)基本使用

  • 下载 react-router-dom

    cnpm i react-router-dom --save
  • 相关组件使用

    • BrowserRouter/HashRouter:

      • 是一个路由容器,所有的路由组件(Route)、导航链接(Link)都要放置在该组件内部。
      • import 导入时使用as可以设置一个别名。
      • BrowserRouter没有#,HashRouter有#。
    • Switch:路由块,只要匹配到一个地址不往下匹配,相当于for循环里面的break
    • Route:路由规则,用于匹配路由组件
    • exact : Route属性,精确匹配路由
    • Link:导航链接,相当于a标签
  • 使用步骤

    • 第一步,规则定义:router.js文件配置路由
    import React from 'react';
    import {BrowserRouter as Router, Route} from "react-router-dom";
    import IndexPage from "./pages/IndexPage";
    import NewsPage from "./pages/NewsPage";
    
    class MyRouter extends React.Component {
      render() {
          return (
            <div>
              {/*路由容器,所有的Route 与Link 组件都要放置其内部*/}
              <Router>
                {/*这里需要一个容器,内部存放多个路由*/}
                <div> 
                    {/*方法1:复杂路由放前边,辅助精确匹配*/}
                   <Route path="/news" component={NewsPage}></Route>
                    {/*方法2:为根路由添加精确匹配*/}
                   <Route path="/" exact component={IndexPage}></Route>
                  
                </div>
              </Router>
            </div>
        );
      }
    }
    export default MyRouter;
    • 第二步,页面导入 :App.js文件导入使用路由
    import React from 'react';
    import MyRouter from "../router/router";
    
    class App extends React.Component{
      render(){
        return (
          <div>
              {/*使用路由规则*/}
              <MyRouter></MyRouter>
          </div>
        )
      }
    }
    
    export default App;
    • 第三步,创建链接标签 :IndexPage.js
    import {Link} from "react-router-dom";
    import MyRouter from "../router/router";
    
    class IndexPage extends React.Component {
        render() {
            return (
                <div>
                   首页
                   <Link to="/">首面</Link>
                   <Link to="/news">新闻页面</Link>
                </div>
              );
        }
      
    }
  • 另一种,组件规则的定义方式,参考官网:https://reactrouter.com/web/guides/quick-start
<Router>
    <Switch>
        <Route path="/about">
            <About />
        </Route>
        <Route path="/users">
            <Users />
        </Route>
        {/*为根路由添加精确匹配*/}
        <Route path="/" exact>
            <Home />
        </Route>
    </Switch>
    </div>
</Router>

(3)路由组件懒加载

react-loadable 实现router.js文件路由组件懒加载

cnpm i -S react-loadable

loadable.js:

/*路由懒加载(异步组件)*/
import React from 'react';
import Loadable from 'react-loadable';
 
//通用的过场组件
const LoadingComponent =(props)=>{
    return (
        <div>loading...</div>
    )
}
 
//过场组件默认采用通用的,若传入了loading,则采用传入的过场组件
export default (loader,loading = LoadingComponent)=>{
    return Loadable({
        loader,
        loading
    });
}

router.js(严格模式下会报出红色警告信息)

import React from "react";
import {BrowserRouter as Router,Switch,Link,Route} from "react-router-dom";
import loadable from "./js/loadable";

// 异步加载组件
const Welcom = loadable(() => import('../pages/Home'))
const UserManage = loadable(() => import('../pages/UserManage'))
const Menus = loadable(() => import('../components/Menu/Menu'))

//……定义/导出 路由规则

(4)路由定义传参

  • 路由规则定义传参,router.js

    //声明要传递的参数
    <Route path="/news/detail/:id/:title" component={NewsDetailPage}></Route>

在Link 的to 属性中传递,indexPage.js

//传递参数值
<Link to="/news/detail/1/新闻详情页1">新闻详情页1</Link>

//在组件内部获取参数
this.props.match.params.id

以上这种写法有局限性,因为在路由后面我们不确定参数个数或丢失参数时,无法匹配任何组件;需要额外定义一个组件进行提示。

(5)导航式路由与传参

  • 利用导航式路由进行query 传参,注意使用BrowerRouter 模式,防止浏览器缓存

第一步,路由规则定义,router.js

//声明要传递的参数
<Route path="/news/detail" component={NewsDetailPage}></Route>

第二步,通过事件处理函数进行导航,事件处理函数中this 的指向,必须为某个路由对应的组件对象

<li onClick={()=>{
       this.props.history.push({
            pathname:"/news/detail",
            //search:"?id=1&title=新闻详情2",
            query:{
                  id:1,
                  title:'新闻详情2'
            }
        })
  }}>新闻详情2</li>
  
  //获取参数
  this.props.location.query.id  
  this.props.location.search    //不会丢失参数,但因为是字符串类型,直接使用不方便

如上query 传参方法可以正确匹配路由,但是,用户初始化或刷新页面,react 路由会丢失query参数,也买你会报错;此时需要给出用户提示和返回按钮:

//设置用户提示
constructor(props){
    super(props);
    if(!this.props.location.query){
        this.props.location.query={};
        this.props.location.query.id=null;
        this.props.location.query.title="请明确您需要查看的具体新闻标题"
    }//可以实现参数丢失时给用户提示的需求
}

//设置返回按钮
render(){

      let flag=this.props.location.query.title=="请明确您需要查看的具体新闻标题"?true:false;//模拟侦听器属性
      return (
          <div>
              新闻详情页面~~~ {this.props.location.query.id}~~~{this.props.location.query.title}
              {flag && (<div><button onClick={()=>{this.props.history.push("/");}}>返回首页</button> </div>)}
          </div>
      )
  }

- 自定义工具类方法接收search参数——search 传参的特点为不丢失参数但使用起来不方便,由此制定一种路由传参的解决方法:

  第一步,定义工具类方法,utils.js
  export default function localParam (search, hash) {
      search = search || window.location.search;
      hash = hash || window.location.hash;
      var fn = function(str, reg) {
          if (str) {
              var data = {};
              str.replace(reg, function($0, $1, $2, $3) {
                  data[$1] = $3;
              });
              return data;
          }
      };
      return {
          search : fn(search, new RegExp("([^?=&]+)(=([^&]*))?", "g")) || {},
          hash : fn(hash, new RegExp("([^#=&]+)(=([^&]*))?", "g")) || {}
      };
  }

   第二步,传参方式简化
  
//this.props.history.push({
//     pathname:"/news/detail",
//     search:"?id=1&title=新闻详情2"
//})  

//传参方式简化
this.props.history.push("/news/detail?id=2&title=新闻详情3")

第三步,获取参数,NewsDetailPage.js
  import localParam from '../assets/js/utils';

  //获取属性
  <div>
       新闻详情页面~~~{localparams(this.props.location.search).search.id}~~~{decodeURI(localparams(this.props.location.search).search.title)}
 </div>

路由传参方式**【总结】**:

1. 路由规则定义params 传参:在组件内部使用 this.props.match.params.id 获取参数
2. 导航式路由query 传参:在组件内部使用 this.props.location.query.id 获取参数
3. 导航式路由search 传参:在组件内部使用工具类方法转换后,localParam(this.props.location. search).search.id 获取参数

- 更多导航式路由方法:

//返回上一步
this.props.history.goBack();

// go()指定返回多少级
this.props.history.go(-1);

//指定跳转至某页面
this.props.history.replace("/");


结合嵌套路由案例对这部分导航式路由进行讲解。


### (6)嵌套路由

- 在父路由组件中,通过Switch匹配子组件:

<div>

<button onClick={this.returnPage.bind(this)}>返回</button>
商品详情页
<Router>
    <ul>
        <li>
            <Link to="/goods/item">商品</Link>
        </li>
        <li>
            <Link to="/goods/review">评价</Link>
        </li>
        <li>
            <Link to="/goods/detail">详情</Link>
        </li>
        <li>
            <Link to="/goods/recommend">推荐</Link>
        </li>
    </ul>

    <Switch>
        <Route path="/goods/item" component={ItemPage}></Route>
        <Route path="/goods/review" component={ReviewPage}></Route>
        <Route path="/goods/detail" component={DetailPage}></Route>
        <Route path="/goods/recommend" component={RecommendPage}></Route>
        <Redirect to="/goods/detail"></Redirect>
    </Switch>
</Router>

</div>


**注意**:父路由组件使用了Redirect 后,定义子路由规则时,不能使用exact 去做精确匹配。

实现商品详情页面的“返回”按钮功能,代码实现:

returnPage() {

// go()指定返回多少级
// this.props.history.go(-1);

// goBack()只返回上一级
// this.props.history.goBack();

// 将历史记录直接替换,返回时就到了指定的页面
this.props.history.replace("/");

}




### (7)路由 HOOK

- withRouter 实现----->导航式路由 HooK

import {withRouter} from "react-router-dom";

//函数组件
function GoodsNav(props){

const goPage=(url)=>{
    props.history.replace(url);
};
return (

    <div>
        <ul>
            <li onClick={goPage.bind(null,'/goods/item')}>商品</li>
            <li onClick={goPage.bind(null,'/goods/details')}>详情</li>
            <li onClick={goPage.bind(null,'/goods/review')}>评价</li>
        </ul>
    </div>

)

}

export default withRouter(GoodsNav);


- useRouteMatch/useParams 实现----->路由params传参 Hook 

  参考案例:https://reactrouter.com/web/example/nesting



### (8)路由认证

模拟路由守卫实现原理,自己制作会员认证组件: AuthRoute.js

import React from 'react';
import {Route,Redirect} from 'react-router-dom';
export default function AuthRoute({ component:Component, path:mypath }) {

return (
    <Route path={mypath} render={props =>
        Boolean(localStorage['isLogin']) ? (
            <Component {...props} />
        ) : (
            <Redirect
                to={{
                    pathname: "/login",
                    state: { from: props.location }
                }}
            />
        )
    }
    />
);

}


路由规则定义:router.js

import {AuthRoute} from './routes/AuthRoute';

<AuthRoute path="/user" component={UserPage}></AuthRoute>






# Redux

### (1)Redux基本认识

我们为什么需要状态管理器Redux?为了方便的组织和管理组件之间的数据。

![redux.png](https://blog.liucl.cn/usr/uploads/2021/02/1501470381.png)

- Redux是什么?
  Redux是一个流行的JavaScript框架,为应用程序提供一个可预测的状态容器。Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。

![redux-store.png](https://blog.liucl.cn/usr/uploads/2021/02/3576962831.png)



- Redux基本原则

  - 单一数据源;
  - state是只读的;
  - 使用纯函数来执行修改;


- Redux核心API

  - createStore:创建数据仓库;
  - store.getState():用于获取store里面的数据;
  - store.dispatch():用于派发action,执行修改动作;
  - store.subscribe():订阅store的修改,只要store发生改变,回调函数就会被执行;



### (2)store的创建和使用

- 使用Redux需要安装该模块

cnpm i redux –save


- 创建reducer(store/reducer.js),**提供数据源**,是一个纯函数

// src/store/reducer.js
const defaultState = {

  username: "小明",
  stulist: ['a', 'b', 'c']

}




//1、state用于提供为仓库提供数据源,需要给定一个初始值
//2、action用于对state 进行更新处理,表示要执行的动作,通过dispatch(action)触发;action被传递到reducer内部来处理,action中必须有一个type字段

const reducer = (state=defaultState) => {
    //action……
    return state;//必须返回处理后的state
}

export default reducer;
  • 创建数据仓库(store/index.js):createStore()

    // src/store/index.js
    import {createStore} from "redux";
    import reducer from "./reducer";
    
    const store = createStore(reducer);
    export default store;
  • 组件内部使用数据:getState()

    //组件内部引入仓库
    import store from "./store/index";
    
    //获取数据
    <h2>{store.getState().username}</h2>

(3)数据的修改和订阅

  • 在组件内部定义要执行的动作action,并触发该动作dispatch。action中type字段是必须的,值通常大写:

    addValue() {
        const action = {
                type: "ADD",
                data: "测试数据"
        }
        store.dispatch(action);
    }
  • 接收动作并修改数据:通过dispatch触发动作后,会将action传递到reducer函数的第二个参数中

    const reducer = (state=defaultState, action) => {
        switch(action.type) {
            case "ADD": 
                state.stulist.push(action.data);
                break;
            default: 
                break;
        }
        return state;
    }
  • 组件中及时响应更新的数据:订阅

    componentDidMount() {
        // 订阅store改变事件
        store.subscribe(this.changeValue.bind(this));
    }
    changeValue() {
        this.setState(store.getState())
    }

subscribe函数可以监听Redux中state的变化,一旦Redux中的state发生了变化,render函数就会被调用,页面就会被重新渲染

ant Design

(1)全量引入方式

官网:https://ant.design/docs/react/introduce-cn

1.npm 安装

$ npm install antd --save

2.示例

import { DatePicker,Button } from 'antd';

ReactDOM.render(
    <div>
        <DatePicker />
        <Button type="primary">ant按钮</Button>
    </div>
    , mountNode);

引入样式

import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'

(2)手动按需加载

import DatePicker from 'antd/lib/date-picker';  // 加载 JS
import 'antd/lib/date-picker/style/css';        // 加载 CSS
import Button from 'antd/lib/button';  // 加载 JS
import 'antd/lib/button/style/css';        // 加载 CSS

(3)自动按需加载

ant Design和ant Design mobile分别对应PC 端与移动端

注:https://mobile.ant.design/docs/react/introduce-cn,移动端index.html需要添加<script>适配代码

1.下载antd和antd-mobile

cnpm i antd --save-dev
cnpm i antd-mobile --save-dev

2.安装插件

cnpm i react-app-rewired customize-cra babel-plugin-import --save-dev

3(1).当项目未运行npm run eject时,更改 package.json 文件

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
}

也就是说,把 "script" 下的 react-scripts 全部改成 react-app-rewired

3(2).当项目已运行npm run eject时,更改 package.json 文件

{
    "babel":{
        "presets": [
              "react-app"
         ],
         "plugins":[
             [
                 "import",
                 {
                     "libraryName":"antd",
                     "style":"css"
                 }
             ]
         ]
    }
}

4.在项目根目录创建一个 config-overrides.js 用于修改默认配置

//antd实现pc端按需引入
const { override, fixBabelImports } = require("customize-cra");
module.exports = override(
    fixBabelImports("import", {
        libraryName: "antd", 
        libraryDirectory: "es", 
        style: 'css' // change importing css to less
    })
)

//antd-mobile实现移动端按需引入
const { override, fixBabelImports } = require("customize-cra");
module.exports = override(
    fixBabelImports("import", {
        libraryName: "antd-mobile", 
        libraryDirectory: "es", 
        style: 'css' // change importing css to less
    })
)
//为index.html 添加移动端适配js-script
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
  <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
  <script>
    if ('addEventListener' in document) {
      document.addEventListener('DOMContentLoaded', function() {
        FastClick.attach(document.body);
      }, false);
    }
    if(!window.Promise) {
      document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
    }
  </script>

5.重新启动项目 npm run start

6.使用组件

import { Button } from 'antd'
import { Button } from 'antd-mobile'

<div>
    <Button>Button</Button>
</div>

项目

问题1:Module not found

项目1.png

解决方案:

1、在cmd小黑框的项目目录下执行以下指令:

npm  install  react-clipboard  --save

2、在EmojiResults.js文件中导入Clipboard 模块的代码改为

import Clipboard from "react-clipboard";

问题2:找不到对应的css文件

项目2.png

1、打开如下网址:

http://www.bootcdn.cn/github-fork-ribbon-css/

2、选择css版本,并打开相应的链接,如:

https://cdn.bootcss.com/github-fork-ribbon-css/0.2.2/gh-fork-ribbon.css

3、在项目的src目录下创建一个名为gh-fork-ribbon.css的样式文件

4、将步骤2中打开的链接中的样式代码粘贴在gh-fork-ribbon.css文件中

5、在index.js文件中修改引入路径为: import "./gh-fork-ribbon.css";

值得在 2018 年尝试的 11 个 React 组件库:https://blog.csdn.net/snsHL9db69ccu1aIKl9r/article/details/79454984

React组件库:https://blog.csdn.net/mjzhang1993/article/details/56666607

React全家桶:https://blog.csdn.net/awaw00/article/category/6692955

组件库SaltUI: https://github.com/salt-ui/saltui

api: https://reacttraining.com/react-router/web/api/Router/children-node

Last modification:February 24th, 2021 at 11:16 am
如果觉得我的文章对你有用,请随意赞赏