react 0基础学习

npm i antd
npm i react-router-dom

react简介

  • React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram(照片交友) 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了
  • Angular1 2009 年 谷歌 MVC 不支持 组件化开发
  • 由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
  • 清楚两个概念:
    1、library(库):小而巧的库,只提供了特定的API;优点就是 船小好掉头,可以很方便的从一个库切换到另外的库;但是代码几乎不会改变;
    2、Framework(框架):大而全的是框架;框架提供了一整套的解决方案;所以,如果在项目中间,想切换到另外的框架,是比较困难的;

前端三大主流框架

  • Angular.js:出来较早的前端框架,学习曲线比较陡,NG1学起来比较麻烦,NG2 ~ NG5开始,进行了一系列的改革,也提供了组件化开发的概念;从NG2开始,也支持使用TS(TypeScript)进行编程;
  • Vue.js最火(关注的人比较多)的一门前端框架,它是中国人开发的,对我我们来说,文档要友好一些;
  • React.js最流行(用的人比较多)的一门框架,因为它的设计很优秀;

React与vue的对比

  • 组件化方面

1、什么是模块化:是从代码的角度来进行分析的;把一些可复用的代码,抽离为单个的模块;便于项目的维护和开发;
2、什么是组件化:是从 UI 界面的角度 来进行分析的;把一些可服用的UI元素,抽离为单独的组件;便于项目的维护和开发;
3、组件化的好处:随着项目规模的增大,手里的组件越来越多;很方便就能把现有的组件,拼接为一个完整的页面;
4、Vue是如何实现组件化的: 通过 .vue 文件,来创建对应的组件, .vue 文件由三部分组成:template 结构;script 行为;style 样式
5、React如何实现组件化:大家注意,React中有组件化的概念,但是,并没有像vue这样的组件模板文件;React中,一切都是以JS来表现的;因此要学习React,JS要合格;ES6 和 ES7 (async 和 await) 要会用;

  • 开发团队方面

1、React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
2、Vue:第一版,主要是有作者 尤雨溪 专门进行维护的,当 Vue更新到 2.x 版本后,也有了一个以 尤雨溪 为主导的开源小团队,进行相关的开发和维护;

  • 社区方面

1、在社区方面,React由于诞生的较早,所以社区比较强大,一些常见的问题、坑、最优解决方案,文档、博客在社区中都是可以很方便就能找到的;
1、Vue是近两年才火起来的,所以,它的社区相对于React来说,要小一些,可能有的一些坑,没人踩过;

  • 移动APP开发体验方面

1、Vue,结合 Weex 这门技术,提供了 迁移到 移动端App开发的体验(Weex,目前只是一个 小的玩具, 并没有很成功的 大案例;)
2、React,结合 ReactNative,也提供了无缝迁移到 移动App的开发体验(RN用的最多,也是最火最流行的);

为什么要学习React

  • 和Angular1相比,React设计很优秀,一切基于JS并且实现了组件化开发的思想;
  • 开发团队实力强悍,不必担心断更的情况;
  • 社区强大,很多问题都能找到对应的解决方案;
  • 提供了无缝转到 ReactNative 上的开发体验,让我们技术能力得到了拓展;增强了我们的核心竞争力;
  • 很多企业中,前端项目的技术选型采用的是React.js;

React中几个核心的概念

  • 虚拟DOM(Virtual Document Object Model)

1、DOM的本质是什么:浏览器中的概念,用JS对象来表示 页面上的元素,并提供了操作 DOM 对象的API;
2、什么是React中的虚拟DOM:是框架中的概念,是程序员 用JS对象来模拟 页面上的 DOM 和 DOM嵌套;
3、为什么要实现虚拟DOM(虚拟DOM的目的):为了实现页面中, DOM 元素的高效更新
4、DOM和虚拟DOM的区别
DOM:浏览器中,提供的概念;用JS对象,表示页面上的元素,并提供了操作元素的API;
虚拟DOM:是框架中的概念;而是开发框架的程序员,手动用JS对象来模拟DOM元素和嵌套关系;本质: 用JS对象,来模拟DOM元素和嵌套关系;目的:就是为了实现页面元素的高效更新;

  • Diff算法

1、tree diff:新旧两棵DOM树,逐层对比的过程,就是 Tree Diff; 当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到;
2、component diff:在进行Tree Diff的时候,每一层中,组件级别的对比,叫做 Component Diff;
如果对比前后,组件的类型相同,则**暂时**认为此组件不需要被更新;
如果对比前后,组件类型不同,则需要移除旧组件,创建新组件,并追加到页面上;
3、element diff:在进行组件对比的时候,如果两个组件类型相同,则需要进行 元素级别的对比,这叫做 Element Diff;

创建基本的webpack4.x项目

  1. 运行npm init -y 快速初始化项目
  2. 在项目根目录创建src源代码目录和dist产品目录
  3. 在 src 目录下创建 index.html
  4. 使用 cnpm 安装 webpack ,运行cnpm i webpack webpack-cli -D(如何安装 cnpm: 全局运行 npm i cnpm -g
  5. 注意:webpack 4.x 提供了 约定大于配置的概念;目的是为了尽量减少 配置文件的体积;
    • 默认约定了:
    • 打包的入口是src -> index.js
    • 打包的输出文件是dist -> main.js
    • 4.x 中 新增了 mode 选项(为必选项),可选的值为:developmentproduction;

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

可以使用create-react-app -V查看当前版本

npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start

打包发布

npm run build 
npm install -g serve
serve build
访问http://localhost:5000/

项目中使用antd

npm i antd
//实现组件的按需打包
npm i react-app-rewired customize-cra babel-plugin-import

在项目中使用 react

  1. 运行 cnpm i react react-dom -S 安装包
    react: 专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
    react-dom: 专门进行DOM操作的,最主要的应用场景,就是ReactDOM.render()

  2. index.html页面中,创建容器:

 <!--html-- 容器,将来,使用 React 创建的虚拟DOM元素,都会被渲染到这个指定的容器中 -->
   <div id="app"></div>
  1. 导入包:
 //js
   import React from 'react'
   import ReactDOM from 'react-dom'
  1. 创建虚拟DOM元素:
//jsx
 // 这是 创建虚拟DOM元素的 API
 <h1 title="啊,五环" id="myh1">你比四环多一环</h1>
 //  第一个参数: 字符串类型的参数,表示要创建的标签的名称
 //  第二个参数:对象类型的参数, 表示 创建的元素的属性节点
 //  第三个参数: 子节点
 const  myh1 = React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
  1. 渲染:
  //js
 // 3. 渲染虚拟DOM元素
 // 参数1: 表示要渲染的虚拟DOM对象
 // 参数2: 指定容器,注意:这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象
   ReactDOM.render(myh1, document.getElementById('app'))

JSX语法

什么是JSX语法:就是符合 xml 规范的 JS 语法;(语法格式相对来说,要比HTML严谨很多) webpack是不能识别jsx的语法的 需要babel包将其转换为React.creatElement()

  1. 如何启用 jsx 语法?
    • 安装能够识别转换jsx语法的包 babel/preset-react
    • 运行cnpm i @babel/preset-react -D
    • 添加 .babelrc 配置文件(新建一个名为.babelrc的文件)
    {
        "presets": ["@babel/preset-    env","@babel/preset-react"],
        "plugins": [
                    "@babel/plugin-transform-runtime",
                    "@babel/plugin-proposal-class-properties"
                    ]
    }
    
    • 添加babel-loader配置项(在webpack.config.js文件中):
    module: { //要打包的第三方模块
         rules: [
           { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ }
         ]
     }
    
    https://www.cnblogs.com/amcy/p/10273929.html
  2. jsx 语法的本质
    并不是直接把 jsx 渲染到页面上,而是 内部先转换成了 createElement 形式,再渲染的;

3.在 jsx 中混合写入 js 表达式
在 jsx 语法中,要把 JS代码写到 { } 中:

  • 渲染数字
  • 渲染字符串
  • 渲染布尔值
  • 为属性绑定值
  • 渲染jsx元素
  • 渲染jsx元素数组
  • 将普通字符串数组,转为jsx数组并渲染到页面上【两种方案】
    第一种方法:把字符串数组转换为元素数组
    第二种方法:使用map{array.map(item=><h3>{item}</h3>)}
    使用key属性
    应用在被for foreach map等循环直接空值得反诉上

例:

//创建虚拟DOM元素:
const obj={name:"赵静怡",age:18,title:"哈哈"}
const myh2=<h2>我是h2</h2>
var flag=true;
const mydiv=<div>
                {obj.name}<!--js语法写在{}里  !-->
                <h1 title={obj.title}>{obj.age}</h1><!--直接绑定属性,不需要“:”  !-->
                {myh2}
                <div>{flag?1:2}</div>
             </div>

//渲染:
ReactDOM.render(mydiv,document.getElementById("app"))

遍历方式:

//1.普通方式:
var arr=[]
var obj=["A","B","C"];
obj.forEach((item,index)=>{
    arr.push(<h1 key={index}>{item}</h1>)
})
const mydiv=<div>{arr}</div>
//渲染
ReactDOM.render(mydiv,document.getElementById("app"))


//2.Map方式:
var obj=["A","B","C"];
const mydiv=<div className="box">
    {obj.map((item,index)=><h2 key={index}>{item}</h2>)}
    <p>
        <input type="checkbox" value="A" id="A"/>
        <label htmlFor="A">A</label>
    </p>
</div>
<!--渲染!-->
ReactDOM.render(mydiv,document.getElementById("app"))
<!--遍历时一定要传入key!-->

注意:

  • 在 jsx 中 写注释:推荐使用{ /* 这是注释 */ }
  • 为 jsx 中的元素添加class类名:需要使用className 来替代 classhtmlFor替换label的for属性
  • 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
  • 在 jsx 语法中,标签必须 成对出现,如果是单标签,则必须自闭和!
  • 当 编译引擎,在编译JSX代码的时候,如果遇到了<那么就把它当作 HTML代码去编译,如果遇到了 {} 就把 花括号内部的代码当作 普通JS代码去编译;

React中创建组件

第1种 - 创建组件的方式

使用构造函数来创建组件,如果要接收外界传递的数据,需要在 构造函数的参数列表中使用props来接收;

必须要向外return一个合法的JSX创建的虚拟DOM;

  • 创建组件:

    function Hello () { 
      // return null 
      return <div>Hello 组件</div>
    }
    
  • 为组件传递数据:

    // 使用组件并 为组件传递 props 数据(绑定自定义属性)
    <Hello  name={dog.name}  age={dog.age}  gender={dog.gender}></Hello>
    // 在构造函数中,使用 props 形参,接收外界 传递过来的数据
    function Hello(props) {
      // props.name = 'zs'
      console.log(props)
       // 结论:不论是 Vue 还是 React,组件中的 props 永远都是只读的;不能被重新赋值;
      return <div>这是 Hello 组件 --- {props.name} --- {props.age} --- {props.gender} </div>
    }
    
  • 抽离组件

    1. 在src目录中新建components目录来存储组件
    2. 在components目录中新建.jsx文件
    3. 在文件中导入Reacct(import React from "react"),因为在组件中使用了jsx的{}
    4. .jsx文件中定义组件,并抛出
      export default function Person(props){
             return <h1>{props.name}</h1>;
      }
      
    5. 在父组件中js中引入组件
      import Person from "@/components/person"
      
  • 在导入组件的时候,如何省略组件的.jsx后缀名:
    在导入组件的时候,配置和使用@路径符号

      // 打开 webpack.config.js ,并在导出的配置对象中,新增 如下节点:
    resolve: {
        extensions: ['.js', '.jsx', '.json'], // 表示,这几个文件的后缀名,可以省略不写
        alias: {
          '@': path.join(__dirname, './src')//配置”@”
        }
      }
    
  • 例:

    //创建组件
    function Person(props){
        return <h1>{props.name}</h1>;//子组件接收数据
    }
    
    const obj={name:"小明",age:20,sex:"男"}
    const mydiv=<div className="box">
            <!--注释 !-->
            {/*<Person name={obj.name}></Person>*/} 
            <!--父组件传参!-->
            <Person {...obj}></Person>
    </div>
    
  • 注意:

    1. 父组件向子组件传递数据
      给子组件名称绑定自定义属性,在子组件中用props来接收
    2. 使用{...obj}属性扩散传递数据
      <Hello {...obj}></Hello>
      
    3. 将组件封装到单独的文件中
    4. 组件的名称首字母必须是大写

第2种 - 创建组件的方式

使用 class 关键字来创建组件
ES6 中 class 关键字,是实现面向对象编程的新形式;

  • 了解ES6中 class 关键字的使用
    1. class 中 constructor 的基本使用
    2. 实例属性和实例方法
    3. 静态属性和静态方法
    4. 使用 extends 关键字实现继承
  • 基于class关键字创建组件
    1. 最基本的组件结构:

      class 组件名称 extends React.Component {
         constructor(){
           super();
           this.state={
           //定义自己私有数据,相当于  vue组件中的data(),this.state中的数据可读可写,props中的数据只能读
           }
         }
         //作用  将里面的内容输出
         render(){
           return 符合标准的jsx语法的串
         }
      }
      

两种创建组件方式的对比
1. 用构造函数创建出来的组件:叫做“无状态组件”,没有自己的私有数据和生命周期函数
2. 用class关键字创建出来的组件:叫做“有状态组件”,有自己的私有数据和生命周期函数
有状态组件和无状态组件之间的**本质区别**就是:有无state属性!

props和state/data的区别
props中的数据都是外界的 并且是可读的不可写的
state/data中的数据都是自己私有的 并且是可读可写的

ES6 class类

如果要生成一个对象实例,需要先定义一个构造函数,然后通过new操作符来完成。构造函数示例:

//函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)
function Person(name,age) {
    this.name = name;
    this.age=age;
}
Person.prototype.say = function(){
    return "我的名字叫" + this.name+"今年"+this.age+"岁了";
}
//通过构造函数创建对象,必须使用new 运算符
var obj=new Person("laotie",88);
console.log(obj.say());//我的名字叫laotie今年88岁了

React中组件传值

  • 父传子

    1. 在父组件中给子组件绑定自定义属性 属性={数据}
    2. 在子组件中用this.props.属性,接收数据
  • 子传父

    1. 在父组件中定义方法(在父组件中设置事件触发的函数)
    2. 在父组件中给子组件绑定自定义事件,接收父组件的方法
    3. 在子组件的某一个方法中通过 this.props.事件名 来调用函数,传递需要的参数即可
  • 兄弟传值
    先子传父 在父传子

  • 例:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class Head extends React.Component{
        constructor(){
            super();
            this.state={
                //定义自己的数据
                color:"red",
                size:"30px"
            }
        }
        //输入内容,返回一个符合标准的jsx语法的串
        render(){
            return  <div style=  {{background:this.state.color,fontSize:this.state.size}}>
                      //设置样式用两个{}
                      大家好我是头
            </div>
        }
    }
    
    
    //父组件
    class Content extends React.Component{
        constructor(){
            super();
            this.state={
                color:"green",
                obj:""
            }
        }
        // ————1.在父组件中定义方法(子传父)
        getData(data) {
            this.setState({
                obj: data
            })
        }
        render(){
            return <div>
                   大家好我是内容
                   {this.state.obj}
                   { /* 1.在父组件中给子组件绑定自定义属性,接收父组件的数据(父传子) */ }
                   { /* ————2.在父组件的子组件中定义自定义事件接收父组件的事件(子传父) */ }
                   <Son1 pro1={this.state.color} getData={(data) => { this.getData(data) }}/>
                   <Son2  pro2={this.state.obj}/>
            </div>
        }
    }
    
    
    //子组件
    class Son1 extends React.Component{
        constructor(){
            super();
            this.state={
                con:"我是son1的数据"
            }
        }
        toFa(data) {
            // ————3.在子组件的事件中接收自定义事件并传参(子传父)
            this.props.getData(data)
        }
        //返回符合要求的jsx串
        render(){
            return <div style={{color:this.props.pro1}}>
                { /* 2.在子组件中接收父组件的传值(父传子) */ }
                我是content的------儿子{this.props.pro1}
                { /* ————4.在子组件中调用事件并传参(子传父) */ }
                <input type='button' value='传值' onClick={(e) => { this.toFa(this.state.con) }}/>
            </div>
        }
    }
    
    
    //子组件
    class Son2 extends React.Component{
        constructor(){
            super();
            this.state={
            
            }
        }
        render(){
            return <div>
                //兄弟组件传值,先子传父,再父传子
                {this.props.pro2}
            </div>
        }
    }
    

React中的样式问题

  • 行内样式
    在组件中是可以加行内样式的 但是 需要一定的格式
    style={ 对象 } 对象可以是一个单独的js变量 也可以直接写在里面
    1. style={ {color:'red',fontSize:'20px'} }
    2. const hcss={color:'red',fontSize:'20px'};
    style={hcss}
    例:

    const hcss={color:"red",fontSize:"30px"};
    class Home extends React.Component{
        render(){
            return <div>
                <p style={{color:"red"}}>直接写在里面</p>
                <p style={hcss}>在外面定义/p>
            </div>
        }
    }
    
  • 外部样式

    1. 在react中可以给样式设置模块化
    2. 在webpack.config.js中给css-loader这个loader设置modules参数{test:/\.css$/,use:['style-loader','css-loader?modules']}
    3. 需要注意的是:如果加上这个参数,在普通的css中会将类选择器、id 选择器,转换成对象的形式 用的时候直接在引入的对象上.类名(对象.类名)
    4. 这样给样式设置了作用域,可以保证不同的组件中可以避免类名的冲突。但是,标签选择器是不会被编译的
    5. 我们自己认为 第三方的样式都是.css结尾的 我们没有必要去给他们做模块化 所以我们约定在webpack.config.js中一般只给后缀名为less和scss的文件设置模块化
      {test:/\.less$/,use:['style-loader','css-loader?modules','less-loader']},       
      {test:/\.scss$/,use:['style-loader','css-loader?  modules','sass-loader']},
      

    例:

    //src里css目录下的list.css
    .title{
        width: 100px;
        height: 40px;
        border: 1px solid blue;
    }
    
    //index.js里
    import list from "@/css/1.css";//引入样式,list是一个对象
    class Home extends React.Component{
        render(){
            return <div>
                        <p className={list.tatle}>模块化后</p>
                        <p className="tatle">没有模块化</p>
            </div>
        }
    }
    

兄弟组件的并列写

将兄弟组件用标签包起来,语法:
ReactDOM.render(<div>组件1组件2</div>,document.getElementById("app"))
例:

ReactDOM.render(<div>
    <Login/>
    <Register/>
    </div>,document.getElementById("app"))

事件绑定及修改组件的值

  • 语法:on事件名 事件名的首字母大写
    onClick={this.方法名}
    方法名是组件中的方法
  • 事件的绑定方式一(不推荐)
    事件名使用的是小驼峰命名,事件={this.方法名}
    //add后不可加()
    <input type="button" value="add" onClick={this.add}/>
    class Home extends React.Component{
          constructor(){
            super();
            this.state={
                name:'哈哈'
            }
            //重新绑定this指向
            this.add=this.add.bind(this);
          }
          add(){
            //修改组件数据
            this.setState({
                name:"呵呵"
            })
            //在事件里,this.state.name中的this指向已经改变,所以需要重新绑定this
            alert(this.state.name);
          }
          render(){
            return <div className={list.tatle}>
                {this.state.data}<!--调用组件数据!-->
            </div>
          }
    }
    
  • 事件的绑定方式二(推荐)
    利用箭头函数绑定事件 因为它没有自己的this指向
    <input type="button" value="add" onClick={(e)=> {this.add(e)}}  />
    add(e){
    //e.target是当前原生dom元素
    }
    
  • 修改组件的值
    1. 若要修改组件自己私有的数据,即this.state里的数据不可以直接修改,需要通过一个异步的方法this.setState({属性:新修改后的值})修改
      this.setState({
          name:e.target.value
      })  
      
    2. this.setstate()方法 注意:
      只是修改某一个或者某几个要更新的数据 而不会覆盖
      此方法是一个异步管理状态的函数

react实现的双向数据绑定

  • 步骤
    1. 给表单元素添加事件 onChange={(e)=>{this.方法名(e)}}
    2. 在组件中定义方法
      方法名(e){
          // e.target 是当前原生的dom元素
          //修改name属性的值
          this.setState({
              name:e.target.value
          }) 
      }
      
    3. 给表单元素设置 value属性 等于刚才修改的那个属性的值value={this.state.name}
  • 案例
    class Test  extends  React.Component{
        constructor(){
            super(),
            this.state={
                son:"谁是儿子",
                name:''
            }
        }
        add(){
            this.setState({
                name:'哈哈'
            })
        }
        //2. 在组件中定义方法
        changename(e){
            this.setState({
                name:e.target.value
            })
        }
        render(){
            return <div style={{background:'red'}}><!--  行内样式  !-->
                      <!-- 1. 给表单元素添加事件  !-->
                      <!-- 3. 给表单元素设置 value属性  !-->
                      <input  type='text'  value={this.state.name}  onChange={(e)=>{this.changename(e)}}/>
                      输入的数据是{this.state.name}
            </div>
        }
    }
    

组件的应用方式

容器组件和单纯组件

class Test extends React.Component{
    render(){
        return <div>
            <!--  加上这个就变成了容器组件用来来显示在组件标签中的孩子  !-->
            {this.props.children}
        </div>
    }
}


class App extends React.Component{
    constructor(){
        super();
        this.state={
            data:"大家好"
        }
    }
    render(){
        return <div>
            <Test>
                <span>呵呵</span>     
            </Test>
        </div>
    }
} 

组件的生命周期

每个组件的实例从创建到运行直到销毁在这个过程中会触发一系列的事件,这些事件就是生命周期的函数
react的生命周期分为三部分:

  • 组件的创建阶段 特点 一辈子只运行一次
    constructor()
    componentWillMount() 在组件挂载到DOM前调用
    Render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
    componentDidMount() 组件挂载到DOM后调用

  • 组件的运行阶段 特点 根据props和state的值得改变执行0此或者多次
    shouldComponentUpdate()
    componentWillUpdate() 组件更新前被调用
    componentDidUpdate() 组件更新后被调用,可以操作组件更新的DOM
    componentWillReceiveProps() 组件接受新的props时调用

  • 组件的销毁阶段 特点 一辈子只运行一次
    componentWillUnmount() 组件被卸载前调用

    image.png

路由

  • 参考网站
    https://www.jianshu.com/p/875225b2ec90
    https://blog.csdn.net/dongyuxu342719/article/details/86542904

  • 前端路由
    Ajax诞生以后,解决了每次用户操作都要向服务器端发起请求重刷整个页面的问题,但随之而来的问题是无法保存Ajax操作状态,浏览器的前进后退功能也不可用,当下流行的两种解决方法是:

    hash:hash原本的作用是为一个很长的文档页添加锚点信息,它自带不改变url刷新页面的功能,所以自然而然被用在记录Ajax操作状态中了。
    history:应该说history是主流的解决方案,浏览器的前进后退用的就是这个,它是window对象下的,以前的history提供的方法只能做页面之间的前进后退,如下:
    history.go(number|URL) 可加载历史列表中的某个具体的页面
    history.forward() 可加载历史列表中的下一个 URL
    history.back() 可加载历史列表中的前一个 URL
    为了让history不仅仅能回退到上一个页面,还可以回到上一个操作状态。HTML5新增了三个方法,其中两个是在history对象里的:
    history.pushState(state, title, url) 添加一条历史记录, state用于传递参数,可以为空。title是设置历史记录的标题,可以为空。url是历史记录的URL,不可以为空。
    history.replaceState(state, title, url) 将history堆栈中当前的记录替换成这里的url,参数同上。
    还有一个事件在window对象下:
    window.onpopstate() 监听url的变化,会忽略hash的变化(hash变化有一个onhashchange事件),但是前面的两个事件不会触发它。
    路由的使用:路由需要的包 react-router-dom 这个包内部依赖于react-router 所以不用手动的安装react-router(cnpm i react-router-dom -S)

  • 前端路由的原理
    根据前端开发人员自定义的路径 显示不同的组件 来达到页面的切换效果 但是设置的这些路径在后端是没有 所以history路由有一个弊端 有时候浏览器会根据自己这个这个前端的路径去访问后台的页面 就会请求不到报404 所以得需要前端的开发人员在webpack中进行设置 这个路由的路径就不用请求后端了 以免发生请求资源不存在

  • location对象的常用的三个属性

    1. location.href 获取当前的路径
    2. locathon.hash 获取当前的hash值
    3. location.search 获取当前的查询参数
  • hash路由
    当hash值发生改变的时候会触发事件 onhashchange事件
    页面路由 window.location.href='www.baidu.com'
    histtory.back()

    window.location='#hash'
    window.onhashchange=function(){
        window.location.hash  当前路由
    }
    
  • history路由
    这个是浏览器的history对象 新增了两个方法 history.pushState(state, title, url)
    history.replaceState(state, title, url)
    window.onpopstate() 监听url的变化,会忽略hash的变化

    history.pushState('name',‘title’,‘/path’)
    history.replaceState('name',‘title’,‘/path’)
    window.onpopstate=function(){
        console.log(window.location.href);
        console.log(window.location.pathname);
        console.log(window.location.query);
        console.log(window.location.search);
    }
    
  • 按需加载 as关键字后面是别名

    import  {
        HashRouter(哈希路由)/BrowserRouter(history路由) as Router,
        Link,
        Navlink,
        Switch,
        Route,
        Redirect
    }  from 'react-router-dom'
    
  • 路由方式
    常用的两种路由
    react-router-dom的Router有四种,常用的两种是:<BrowserRouter> H5路由<HashRouter>hash路由
    <Route> 设置路由规则
    <Switch> 路由选项,里面包含匹配规则 会匹配到第一个匹配到的路由 如果和<Router> 配合使用 必须包含在<Router>的里面 在子组件中也可以单独使用 在渲染虚拟dom时候放在<Router> 里面
    <Redirect> 重定向
    <Link>/<NavLink> 路由导航
    <Link> 链接 转换为a标签
    <NavLink> 导航链接 可以设置样式 更适合导航
    注意:NavLink和Link的区别 NavLink在选中的时候会添加选中类 .active 可以在这个类上设置选中的样式

    <NavLink to="/list" activeClassName={appscss.active}>列表</NavLink>
    
  • react-router中的exact和strict
    参考网站:https://www.jianshu.com/p/afe46a62a7ba

    exact
    exact默认为false,如果为true时,需要和路由相同时才能匹配,但是如果有斜杠也是可以匹配上的。
    如果在父路由中加了exact,是不能匹配子路由的,建议在子路由中加exact
    strict
    <Route strict path="/one" component={About} />
    strict默认为false,如果为true时,路由后面有斜杠而url中没有斜杠,是不匹配的

    案例:


    image.png

    总结:

    如果没有子路由的情况,建议大家配都加一个exact;如果有子路由,建议在子路由中加exact,父路由不加;
    而strict是针对是否有斜杠的,一般可以忽略不配置。

  • 定义方式
    <Route path='自定义' compontent/>要被包裹在HashRouter中

  • 案例
    需求:当访问 / 显示出home组件
    步骤:

    1. 引入包
      import  {HashRouter as Router,Route} from 'react-router-dom'
      
    2. 做了容器组件 App 在render中 {this.props.children}
    3. 做的home组件
    4. 配置的规则,这个规则属于一个入口的规则 ,如果在子组件中需要路由就去子组件中配置即可
      <Router>
          <App>
              <Route path='/' component={Home} />
          </App>
      </Router>
      

    需求: 在home组件中,有两个标题:用户列表、商品列表,点击显示不同页面
    步骤:

    1. 在home组件中设置 <NavLink><NavLink> 做导航链接
    2. 在home组件中配置规则
      <Switch>
          <Route path='/user' component={User} />
          <Route path='/goods' component={Goods} />
          <Redirect  from='/' to='/user'/>
      </Switch>
      

路由传参

  • params

    <Route path='/path/:name' component={Path}/>
    <!--  跳转  !-->
    <link to="/path/2">xxx</Link>
    this.props.history.push({pathname:"/path/" + name});
    <!--  读取参数  !-->
    this.props.match.params.name
    

    优势:刷新地址栏,参数依然存在
    缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

  • query

    <Route path='/query' component={Query}/>
    <!--  跳转  !-->
    <Link to={{ path : ' /query' , query : { name : 'sunny' }}}>
    this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
    <!--  读取参数  !-->
    this.props.location.query.name
    

    优势:传参优雅,传递参数可传对象;
    缺点:刷新地址栏,参数丢失

  • state

    <Route path='/sort ' component={Sort}/>
    <!--  跳转  !-->
    <Link to={{ path : ' /sort ' , state : { name : 'sunny' }}}> 
    this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
    <!--  读取参数  !-->
    this.props.location.query.state 
    

    优缺点同query

  • search

    <Route path='/web/departManange ' component={DepartManange}/>
    <!--  跳转  !-->
    <link to="web/departManange?tenantId=12121212">xxx</Link>
    this.props.history.push({pathname:"/web/departManange?tenantId" + row.tenantId});
    <!--  读取参数  !-->
    this.props.location.search
    

    优缺点同params

  • 方式一:通过params

    1. 路由表中
      <Route path=' /sort/:id '   component={Sort}></Route>
      
    2. Link处
      // HTML方式
      <Link to={ ' /sort/ ' + ' 2 ' }  activeClassName='active'>XXXX</Link>
       //JS方式
       this.props.history.push(  '/sort/'+'2'  )
      
    3. sort页面
      通过:this.props.match.params.id,就可以接受到传递过来的参数(id)
  • 方式二:通过query
    前提:必须由其他页面跳过来,参数才会被传递过来
    注:不需要配置路由表。路由表中的内容照常:
    <Route path='/sort' component={Sort}></Route>

    1. Link处
      //HTML方式
      <Link to={{ path : ' /sort ' , query : { name : 'sunny' }}}>
      //JS方式
      this.props.history.push({ path : '/sort' ,query : { name: ' sunny'} })
      
    2. sort页面
      this.props.location.query.name
      
  • 方式三:通过state
    同query差不多,只是属性不一样,而且state传的参数是加密的,query传的参数是公开的,在地址栏

    1. Link 处
      //HTML方式:
      <Link to={{ path : ' /sort ' , state : { name : 'sunny' }}}> 
      //JS方式:
      this.props.history.push({ pathname:'/sort',state:{name : 'sunny' } })
      
    2. sort页面
      this.props.location.state.name
      

本地存储

componentWillMount() 初始化的时候在这个方法中读取localStorage的值

绑定回车事件

onKeyPress={(e)=>this.addlist(e)}

addlist(e){
   if(e.which!=13) return false
   console.log('我点击了回车键');
}

获取真实dom

首先给元素 添加一个 ref属性 ref="变量"
在方法中通过 this.refs.变量 来获取真实的dom元素
http://www.todolist.cn/
https://www.bootcdn.cn/
https://webthemez.com/demo/insight-free-bootstrap-html5-admin-template/index.html

Redux

Redux是针对JavaScript应用的可预测状态容器
就是一个应用的state管理库 组件之间数据共享
redux 是一个状态管理器,那什么是状态呢?状态就是数据
可预测性(predictable): 因为Redux用了reducer与纯函数(pure function)的概念,每个新的state都会由旧的state建来一个全新的state。因而所有的状态修改都是”可预测的”。
状态容器(state container): state是集中在单一个对象树状结构下的单一store,store即是应用程序领域(app domain)的状态集合。
JavaScript应用: 这说明Redux并不是单指设计给React用的,它是独立的一个函数库,可通用于各种JavaScript应用。
Redux模型中的几个组成对象action 、reducer、store
action:官方的解释是action是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源;要通过本地或远程组件更改状态,需要分发一个action;
reducer:action发出了做某件事的请求,只是描述了要做某件事,并没有去改变state来更新界面,reducer就是根据action的type来处理不同的事件;
store:store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态。
运行流程
在Redux中,所有的数据(比如state)被保存在一个被称为store的容器中 ,在一个应用程序中只能有一个store对象。当一个store接收到一个action,它将把这个action代理给相关的reducer。reducer是一个纯函数,它可以查看之前的状态,执行一个action并且返回一个新的状态

  • 使用
    1. 下载cnpm i redux -S

    2. 引入import {createStore} from "redux"从redux包中引入createStore()方法

    3. 创建一个纯函数

      /**创建了一个名为reducer的方法,
      第一个参数state是当前保存在store中的数据,
      第二个参数action是一个容器,用于: 
      type - 一个简单的字符串常量,例如ADD, UPDATE,         DELETE等。
      payload - 用于更新状态的数据 **/
      
      function reducer(state,action){
          state  之前的状态
          action  发生改变后的状态
      }
      
    4. 创建一个仓库

      //创建一个Redux存储区,它只能使用reducer作为参数来构造。存储在Redux存储区中的数据可以被直接访问,但只能通过提供的reducer进行更新。
      const store=createStore(reducer,{
          count:10
      });
      

      补充知识:combineReducers
      目前我们创建的reducer是通用的,那么我们如何使用多个reducer呢?此时我们可以使用Redux包中提供的combineReducers函数。做如下内容修改:

      // src/index.js
      import { createStore } from "redux";
      import { combineReducers } from 'redux';
      
      const productsReducer = function(state=[], action) {
        return state;
      }
      
      const cartReducer = function(state=[], action) {
        return state;
      }
      
      const allReducers = {
        products: productsReducer,
        shoppingCart: cartReducer
      }
      
      const rootReducer = combineReducers(allReducers);
      
      let store = createStore(rootReducer);
      
    5. 获取数据
      store是一个对象,但是要获取这个对象中的所有储存的数据,需要调用它的方法:store.getState(),而state就是store.getState()得到的数据。
      在组件中使用这个数据 {store.getState().名}
      例:
      {store.getState().count}

    6. 修改数据
      //如果是修改数据,需要使用store的dispatch派发一个action,action需要两个参数
      type:通过type区分是对state做什么操作
      payload:传递的数据
      store.dispatch({
      type:'ADD', //自己定义的一个标识当前操作的串
      payload:1 //传递的数据
      })
      function reducer(state,action){
      if(action.type=="ADD"){
      state.count+=action.payload;
      }
      return state;
      }

    7. 更新页面
      //这个函数执行完毕后只是store中的数据已发生改变,页面还没有发生变化
      //页面发生变化
      store.subscribe(()=>{
      this.setState({})
      })
      //写在constructor()里面

官网介绍
import { createStore } from 'redux'
/**

  • 这是一个 reducer,形式为 (state, action) => state 的纯函数。
  • 描述了 action 如何把 state 转变成下一个 state。
  • state 的形式取决于你,可以是基本类型、数组、对象、
  • 甚至是 Immutable.js 生成的数据结构。惟一的要点是
  • 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
  • 下面例子使用 switch 语句和字符串来做判断,但你可以写帮助类(helper)
  • 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
    */
    function counter(state = 0, action) {
    switch (action.type) {
    case 'INCREMENT':
    return state + 1
    case 'DECREMENT':

图片路径

后来看了看create-react-app的官网,官网明确说出最好将图片样式等放在public文件夹中,并且据我测试,文件夹名最好是assets


WechatIMG69.png

WechatIMG71.png

修改ant默认样式

拿到这个名字,直接在代码上进行修改。
在我们拿到的这个名字前面加:global(),把名字写在括号里面,直接写上你想要修改的样式就可以了。
:global(.has-error .ant-form-explain){
position: absolute;
}
这样直接修改会破坏其他的组件的样式,可以在你想要修改样式的标签外面定义一个父级元素,在用一个父级选择器就可以了。·

封装axios

http.js

import axios from 'axios'
import { message } from 'antd'
/*
函数返回值是promise对象
优化:
一、统一处理请求异常
    1.在外层抱一个自己创建的promise对象
    2.在请求出错时(catch),不reject(error),而是显示错误提示
二、异步得道不是response,而是response.data
    在异步请求成功resolve时,resolve(response.data)
*/
export default function http (url, data = {}, type = 'GET') {
    return new Promise((resolve, reject) => {
        let promise
        // 1.执行异步ajax请求
        if (type === "GET") {
            promise = axios.get(url, {
                params: data//配置对象
            })
        } else {
            promise = axios.post(url, data)
        }
        promise.then(response => {
            // 2.如果成功了,调用resolve(value),传入response
            resolve(response.data)
        }).catch(error => {
            // 3.如果失败了,不调用reject(error),而是提示异常信息
            // 如果调用reject就会触发catch,但是我们想要统一处理错误提示
            message.error('请求出错了了', error)
        })
    })
}

login.js

import http from './http.js'
export const login = (data) => http('/login', data, 'POST')

login.jsx

import React, { Component } from 'react'
import { Form, Input, Button, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import './login.scss'
import { login } from '../../api/login'

// 高阶函数
// 高阶组件
export default class Login extends Component {
    onFinish = async (values) => {
        console.log('Received values of form: ', values);
        // 1----
        // login(values).then(res => {
        //     console.log('ok', res)
        // }).catch(err => {
        //     console.log('no', err)
        // })

        // 2----
        // try {
        //     const response = await login(values)
        //     console.log('请求ok',response)
        // } catch (err) {
        //     console.log('请求出错',err)
        // }

        // 3---
        // const response = await login(values)
        // console.log('请求ok',response)

        // 4---
        // const response = await login(values)
        // const result =response.data
        // if(result.meta.status_code===422){
        //     message.success('登录成功')
        //     // replace不能再回到login,push还可以回到login
        //     this.props.history.replace('/')
        // }else{
        //     message.error('失败',result.msg)
        // }

        // 5---
        const result = await login(values)
        if(result.meta.status_code===422){
            message.success('登录成功')
            // replace不能再回到login,push还可以回到login
            this.props.history.replace('/')
        }else{
            message.error('失败',result.msg)
        }
    };
    render () {
        return (
            <div className='login'>
                <header className='header'>
                    <img src="assets/logo.png" alt="" />
                    <h1>电考:后台管理系统</h1>
                </header>
                <section className='content'>
                    <h2>用户登录</h2>
                    <Form
                        name="normal_login"
                        className="login-form"
                        initialValues={{
                            remember: true,
                        }}
                        onFinish={this.onFinish}
                    >
                        <Form.Item
                            name="username"
                            rules={[{ required: true, message: '请输入用户名!', },]}>
                            <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="用户名" />
                        </Form.Item>
                        <Form.Item
                            name="password"
                            rules={[{ required: true, message: '请输入密码!', },]}>
                            <Input prefix={<LockOutlined className="site-form-item-icon" />} type="password" placeholder="密码" />
                        </Form.Item>
                        <Form.Item>
                            <Button type="primary" htmlType="submit" className="login-form-button">登录</Button>
                        </Form.Item>
                    </Form>
                </section>
            </div>
        )
    }
}

/*  async和await
简化promise对象的使用:
     1.不用再使用.then()来指定成功/失败的回调函数
     2.以同步编码(没有回调函数)方式实现异步流程

await:在返回promise的表达式左侧await,不想要promise,想要promise异步执行的成功的value数据
async:await所在函数(最近的)定义的左侧写async

onFinish = async (values) => {
    try {
        const response = await login(values)
        console.log('请求ok',response)
    } catch (err) {
        console.log('请求出错',err)
    }
};

onFinish = (values) => {
        console.log('Received values of form: ', values);
     login(values).then(res => {
        console.log('ok', res)
     }).catch(err => {
        console.log('no', err)
     })

    };
*/

431605993228_.pic.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • 包含 react基础 react传值 react路由 和redux管理 和react-redux reactDom...
    栀璃鸢年_49a3阅读 1,067评论 0 1
  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 976评论 0 1
  • HTML模版 之后出现的React代码嵌套入模版中。 1. Hello world 这段代码将一个一级标题插入到指...
    ryanho84阅读 6,143评论 0 9
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,758评论 0 24
  • React简介 (1)简介 React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaSc...
    鱼鱼吃猫猫阅读 1,587评论 1 6