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项目
- 运行
npm init -y
快速初始化项目 - 在项目根目录创建
src
源代码目录和dist
产品目录 - 在 src 目录下创建
index.html
- 使用 cnpm 安装 webpack ,运行
cnpm i webpack webpack-cli -D
(如何安装cnpm
: 全局运行npm i cnpm -g
) - 注意:webpack 4.x 提供了 约定大于配置的概念;目的是为了尽量减少 配置文件的体积;
- 默认约定了:
- 打包的入口是
src
->index.js
- 打包的输出文件是
dist
->main.js
- 4.x 中 新增了
mode
选项(为必选项),可选的值为:development
和production
;
使用 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
运行
cnpm i react react-dom -S
安装包
react: 专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
react-dom: 专门进行DOM操作的,最主要的应用场景,就是ReactDOM.render()
在
index.html
页面中,创建容器:
<!--html-- 容器,将来,使用 React 创建的虚拟DOM元素,都会被渲染到这个指定的容器中 -->
<div id="app"></div>
- 导入包:
//js
import React from 'react'
import ReactDOM from 'react-dom'
- 创建虚拟DOM元素:
//jsx
// 这是 创建虚拟DOM元素的 API
<h1 title="啊,五环" id="myh1">你比四环多一环</h1>
// 第一个参数: 字符串类型的参数,表示要创建的标签的名称
// 第二个参数:对象类型的参数, 表示 创建的元素的属性节点
// 第三个参数: 子节点
const myh1 = React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
- 渲染:
//js
// 3. 渲染虚拟DOM元素
// 参数1: 表示要渲染的虚拟DOM对象
// 参数2: 指定容器,注意:这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象
ReactDOM.render(myh1, document.getElementById('app'))
JSX语法
什么是JSX语法:就是符合 xml 规范的 JS 语法;(语法格式相对来说,要比HTML严谨很多) webpack是不能识别jsx的语法的 需要babel包将其转换为React.creatElement()
-
如何启用 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文件中):
https://www.cnblogs.com/amcy/p/10273929.htmlmodule: { //要打包的第三方模块 rules: [ { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ } ] }
- 安装能够识别转换jsx语法的包
-
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
来替代class
;htmlFor
替换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> }
-
抽离组件
- 在src目录中新建
components
目录来存储组件 - 在components目录中新建
.jsx
文件 - 在文件中导入
Reacct(import React from "react")
,因为在组件中使用了jsx的{} - 在
.jsx
文件中定义组件,并抛出export default function Person(props){ return <h1>{props.name}</h1>; }
- 在父组件中js中引入组件
import Person from "@/components/person"
- 在src目录中新建
-
在导入组件的时候,如何省略组件的
.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>
-
注意:
- 父组件向子组件传递数据
给子组件名称绑定自定义属性,在子组件中用props来接收
- 使用
{...obj}
属性扩散传递数据<Hello {...obj}></Hello>
- 将组件封装到单独的文件中
- 组件的名称首字母必须是大写
- 父组件向子组件传递数据
第2种 - 创建组件的方式
使用 class 关键字来创建组件
ES6 中 class 关键字,是实现面向对象编程的新形式;
- 了解ES6中 class 关键字的使用
- class 中
constructor
的基本使用 - 实例属性和实例方法
- 静态属性和静态方法
- 使用
extends
关键字实现继承
- class 中
- 基于
class
关键字创建组件-
最基本的组件结构:
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中组件传值
-
父传子
- 在父组件中给子组件绑定自定义属性 属性={数据}
- 在子组件中用this.props.属性,接收数据
-
子传父
- 在父组件中定义方法(在父组件中设置事件触发的函数)
- 在父组件中给子组件绑定自定义事件,接收父组件的方法
- 在子组件的某一个方法中通过 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> } }
-
外部样式
- 在react中可以给样式设置模块化
- 在webpack.config.js中给css-loader这个loader设置modules参数
{test:/\.css$/,use:['style-loader','css-loader?modules']}
- 需要注意的是:如果加上这个参数,在普通的css中会将类选择器、id 选择器,转换成对象的形式 用的时候直接在引入的对象上.类名
(对象.类名)
- 这样给样式设置了作用域,可以保证不同的组件中可以
避免类名的冲突
。但是,标签选择器是不会被编译的 - 我们自己认为 第三方的样式都是.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元素 }
- 修改组件的值
- 若要修改组件自己私有的数据,即this.state里的数据不可以直接修改,需要通过一个异步的方法
this.setState({属性:新修改后的值})
修改this.setState({ name:e.target.value })
- this.setstate()方法 注意:
只是修改某一个或者某几个要更新的数据 而不会覆盖
此方法是一个异步管理状态的函数
- 若要修改组件自己私有的数据,即this.state里的数据不可以直接修改,需要通过一个异步的方法
react实现的双向数据绑定
-
步骤:
- 给表单元素添加事件
onChange={(e)=>{this.方法名(e)}}
- 在组件中定义方法
方法名(e){ // e.target 是当前原生的dom元素 //修改name属性的值 this.setState({ name:e.target.value }) }
- 给表单元素设置 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()
组件被卸载前调用
路由
参考网站
//www.greatytc.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对象的常用的三个属性
-
location.href
获取当前的路径 -
locathon.hash
获取当前的hash值 -
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
参考网站://www.greatytc.com/p/afe46a62a7baexact
exact默认为false,如果为true时,需要和路由相同时才能匹配,但是如果有斜杠也是可以匹配上的。
如果在父路由中加了exact,是不能匹配子路由的,建议在子路由中加exact
strict
<Route strict path="/one" component={About} />
strict默认为false,如果为true时,路由后面有斜杠而url中没有斜杠,是不匹配的案例:
总结:
如果没有子路由的情况,建议大家配都加一个exact;如果有子路由,建议在子路由中加exact,父路由不加;
而strict是针对是否有斜杠的,一般可以忽略不配置。
定义方式
<Route path='自定义' compontent/>要被包裹在HashRouter中
-
案例
需求:当访问 / 显示出home组件
步骤:- 引入包
import {HashRouter as Router,Route} from 'react-router-dom'
- 做了容器组件 App 在render中 {this.props.children}
- 做的home组件
- 配置的规则,这个规则属于一个入口的规则 ,如果在子组件中需要路由就去子组件中配置即可
<Router> <App> <Route path='/' component={Home} /> </App> </Router>
需求: 在home组件中,有两个标题:用户列表、商品列表,点击显示不同页面
步骤:- 在home组件中设置 <NavLink><NavLink> 做导航链接
- 在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
- 路由表中
<Route path=' /sort/:id ' component={Sort}></Route>
- Link处
// HTML方式 <Link to={ ' /sort/ ' + ' 2 ' } activeClassName='active'>XXXX</Link> //JS方式 this.props.history.push( '/sort/'+'2' )
- sort页面
通过:this.props.match.params.id
,就可以接受到传递过来的参数(id)
- 路由表中
-
方式二:通过query
前提:必须由其他页面跳过来,参数才会被传递过来
注:不需要配置路由表。路由表中的内容照常:
<Route path='/sort' component={Sort}></Route>
- Link处
//HTML方式 <Link to={{ path : ' /sort ' , query : { name : 'sunny' }}}> //JS方式 this.props.history.push({ path : '/sort' ,query : { name: ' sunny'} })
- sort页面
this.props.location.query.name
- Link处
-
方式三:通过state
同query差不多,只是属性不一样,而且state传的参数是加密
的,query传的参数是公开
的,在地址栏
- Link 处
//HTML方式: <Link to={{ path : ' /sort ' , state : { name : 'sunny' }}}> //JS方式: this.props.history.push({ pathname:'/sort',state:{name : 'sunny' } })
- sort页面
this.props.location.state.name
- Link 处
本地存储
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并且返回一个新的状态
-
使用:
下载
cnpm i redux -S
引入
import {createStore} from "redux"
从redux包中引入createStore()方法-
创建一个纯函数
/**创建了一个名为reducer的方法, 第一个参数state是当前保存在store中的数据, 第二个参数action是一个容器,用于: type - 一个简单的字符串常量,例如ADD, UPDATE, DELETE等。 payload - 用于更新状态的数据 **/ function reducer(state,action){ state 之前的状态 action 发生改变后的状态 }
-
创建一个仓库
//创建一个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);
获取数据
store是一个对象,但是要获取这个对象中的所有储存的数据,需要调用它的方法:store.getState()
,而state就是store.getState()得到的数据。
在组件中使用这个数据{store.getState().名}
例:
{store.getState().count}
修改数据
//如果是修改数据,需要使用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;
}更新页面
//这个函数执行完毕后只是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
修改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)
})
};
*/