06-第六阶段笔记
1、React入门
1.1、React简介
- 用于动态构建用户界面的 JavaScript 库(只关注于视图)
- 由Facebook开源
1.1.1、官网
- 英文官网: https://reactjs.org/
- 中文官网:react.docschina.org/
1.1.2、React的特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
1.1.3、React高效的原因
- 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- DOM Diffing算法, 最小化页面重绘。
1.2、React的基本使用
1.2.1、基本使用
<!-- 准备好一个容器,用于让react渲染用 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
//下面一定要将javascript改为babel,含义是:让babel翻译script标签中的代码。
<script type="text/babel">
//1.创建虚拟DOM
//此处一定不要写引号,因为VDOM不是字符串!!!
const VDOM = <h1>Hello,React</h1>
//2.使用react语法将VDOM转为真实DOM,插入页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
1.2.2、相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX语法代码转为JS代码的库。
1.2.3、创建虚拟DOM的两种方式
-
纯JS方式(一般不用)
<script type="text/javascript"> //1.创建虚拟DOM const VDOM = ( React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello,React'))) </script> -
JSX方式
<script type="text/babel"> //1.创建虚拟DOM const VDOM = ( <h1 id="title"> <span>Hello,React</span> </h1> ) </script>
1.2.4、虚拟DOM与真实DOM
-
React提供了一些API来创建一种 “特别” 的一般js对象
//下面创建的就是一个简单的虚拟DOM对象 const VDOM = React.createElement('xx',{id:'xx'},'xx') -
虚拟DOM对象最终都会被React转换为真实的DOM
-
我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。
1.3、React JSX
1.3.1、JSX简介
-
全称: JavaScript XML
-
react定义的一种类似于XML的JS扩展语法: JS + XML本质是JS方法的语法糖
React.createElement(component,props, ...children) -
作用: 用来简化创建虚拟DOM
- 写法:var ele =
Hello JSX!
- 注意1:它不是字符串, 也不是HTML/XML标签
- 注意2:它最终产生的就是一个JS对象
- 写法:var ele =
-
标签名任意: HTML标签或其它标签
-
标签属性任意: HTML标签属性或其它
1.3.2、JSX基本语法规则
-
创建虚拟DOM时,不要用引号。
-
标签中想混入js表达式,需要用{}包裹。
-
根标签必须只有一个
-
标签必须闭合
-
样式的类名,不要用class,必须用className
-
内联的样式要用 style={{}}形式去写
-
标签可以随意的编写:
-
若标签首字母是【小写】的,则react会尝试将当前的jsx标签对应成一个html标签
- 若对应成了,直接渲染,展示。
- 若无法对应,直接报错!
-
若标签首字母是【大写】的,则react会查找Haha组件的定义的位置
- 若找见了,直接渲染Haha组件
- 若未找见,报错(Haha is not defined)
-
1.3.3、渲染虚拟DOM(元素)
-
语法:
ReactDOM.render(virtualDOM, containerDOM) -
作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
-
参数说明
- 参数一: 纯js或jsx创建的虚拟dom对象
- 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
1.4、模块与组件、模块化与组件化的理解
1.4.1、模块
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用js, 简化js的编写, 提高js运行效率
1.4.2、组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
1.4.3、模块化
- 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
1.4.4、组件化
- 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
2、React面向组件编程
2.1、基本理解和使用
2.1.1、使用React开发者工具调试
- React Developer Tools (Mate提供(原facebook))
2.1.2、函数式组件
-
函数式组件实例:
-
<script type="text/babel"> //1.定义组件(函数式组件) function Demo(){ console.log(this); //此处的this是undefined,因为经过babel的编译后,开启了严格模式。 return <h2>函数定义的组件(适用于【简单组件】)</h2> } //2.渲染组件到页面 ReactDOM.render( <Demo/>,document.getElementById('test') ) </script> -
函数式组件标签的渲染基本流程
- React发现了标签,去寻找Demo组件定义的位置,发现Demo是用函数定义的。
- React调用Demo并获取Demo返回的虚拟DOM,随后转为真实DOM,随后渲染到页面。
2.1.3、类式组件
-
类式组件实例
-
<script type="text/babel"> //定义组件,extends 继承 class Demo extends React.Component{ //render是放在Demo的原型对象上,是给Demo的实例对象用的。 render(){ console.log(this); //this是Demo的实例对象 <==> Demo组件实例对象 return <h2>类定义的组件(适用于【复杂组件】)</h2> } } //渲染组件到页面 ReactDOM.render( <Demo/>,document.getElementById('test') ) </script> -
类组件标签的渲染基本流程
- React发现了标签,去寻找Demo组件定义的位置,发现Demo是用类定义的。
- React new了一个Demo实例对象--d
- 通过d调用到了Demo原型上的render方法,并获取到了返回的虚拟DOM,随后转为真实DOM,放在页面。
2.1.3、注意内容
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
2.2、组件的三大核心属性一:state
2.2.1、state简介
- state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
- 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.2.1、state实例应用一(原始版)
<script type="text/babel">
//1.定义组件----类式组件
class Weather extends React.Component{
//构造器调用几次?-------- 看你组件用几次
constructor(props){
console.log('constructor');
super(props)
//初始化状态,isHot用于标识天气热不热
this.state = {isHot:true,wind:'微风'}
//解决this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
//changeWeather调用几次?-------- 看你点几次
changeWeather(){
console.log('changeWeather');
//若构造器中不做处理,那么下面的this是undefined,因为changeWeather
//不是通过实例调用的,而是作为点击的回调去使用,
//且类中的方法自动开启了严格模式。
//console.log('changeWeather的this是',this);
//严重注意:状态(state)中的值是不能直接修改的!!!!
//下面这一行就是直接修改
//this.state.isHot = true
//获取原来的state中的isHot值
const {isHot} = this.state
//更新状态
//此处更新状态是一个合并的动作,不是替换
this.setState({isHot:!isHot})
}
//render调几次?--------- 1+n次(n是更新状态的次数)
render(){
console.log('render');
return (
<h1 onClick={this.changeWeather}>
今天天气很{this.state.isHot ? '炎热' : '凉爽'}
</h1>)
}
}
//2.渲染组件
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
2.2.2、state实例应用二(简化版)
<script type="text/babel">
//1.定义组件----类式组件
class Weather extends React.Component{
state = {isHot:true,wind:'微风23'} //初始化状态
//事件的回调都需要写成赋值语句+箭头函数的形式
changeWeather = ()=>{
console.log('changeWeather');
const {isHot} = this.state
this.setState({isHot:!isHot})
}
render (){
return (
<h1 onClick={this.changeWeather}>
今天天气很{this.state.isHot ? '炎热' : '凉爽'}
</h1>
)
}
}
//2.渲染组件
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
2.2.3、state总结
-
组件中render方法中的this为组件实例对象
-
组件自定义的方法中this为undefined,如何解决?
- 强制绑定this: 通过函数对象的bind()
- 箭头函数
-
状态数据,不能直接修改或更新
2.3、组件三大核心属性二: props
2.3.1、props简介
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
2.3.2、props的基本使用
-
<script type="text/babel"> //定义组件(类) class Person extends React.Component{ render(){ console.log(this); const {name,age,sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } } //渲染组件 ReactDOM.render( <Person name="tom" sex="女" age="18"/>, document.getElementById('test') ) const p1 = { name:'程老师', sex:'男', age:18 } //下面的...p1,并不是原生js里的{...p1}, //babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性!! ReactDOM.render( <Person {...p1}/>,document.getElementById('test2') ) </script>
2.3.3、类组件对props进行限制
-
//类组件有this,可以定义在组件内部 static propTypes = { name:PropTypes.string, //限制name必须为字符串类型 sex:PropTypes.string.isRequired,//限制必要属性,且必须为字符串类型 age:PropTypes.number,//限制age必须为数值类型 address:PropTypes.string, //限制address必须为字符串类型 } //对传给Person组件的props进行默认值的设置 static defaultProps = { address:'中国' }
2.3.4、函数式组件使用props
-
//函数内没有this,所有只能定义在函数组件外 Person.propTypes = { name:PropTypes.string, //限制name必须为字符串类型 sex:PropTypes.string.isRequired,//限制必要属性,且必须为字符串类型 age:PropTypes.number,//限制age必须为数值类型 address:PropTypes.string, //限制address必须为字符串类型 } //对传给Person组件的props进行默认值的设置 Person.defaultProps = { address:'中国' }
2.4、组件三大核心属性三: ref
组件内的标签可以定义ref属性来标识自己
2.4.1、字符串形式的ref
-
//定义组件 class Demo extends React.Component{ show = ()=>{ //获取用户的输入,input1是真实DOM节点!! const {input1} = this.refs //提示数据 alert(input1.value) } render(){ return ( <div> <input ref="input1"/> <button onClick={this.show}>点我提示数据</button> </div> ) } }
2.4.2、回调形式的ref(常用)
-
//定义组件 class Demo extends React.Component{ showData = ()=>{ //获取用户的输入,input1是真实DOM节点!! const {input1} = this //提示数据 alert(input1.value) } render(){ return ( <div> <input ref={c => this.input1 = c} type="text" / <button onClick={this.showData}>点我提示数据</button> </div> ) } }
2.4.3、createRef创建ref容器
-
//定义组件 class Demo extends React.Component{ //使用createRef,可以创建一个存储节点的容器,此容器是专人专用的。 //container是放在组件实例身上的 container1 = React.createRef() container2 = React.createRef() showData = ()=>{ const {current} = this.container1 alert(current.value) } render(){ return ( <div> <input ref={this.container1} type="text" /> <button onClick={this.showData}>点我提示数据</button> <input ref={this.container2} onBlur={this.showData2} type="text"/> </div> ) } }
2.5、React事件处理
-
通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
-
通过event.target得到发生事件的DOM元素对象
-
//定义组件 class Demo extends React.Component{ showData2 = (event)=>{ const {value} = event.target alert(value) } render(){ return ( <div> <input onBlur={this.showData2} type="text" placeholder="失去焦点提示输入"/> </div> ) } }
2.6、收集表单数据
-
非受控组件
-
//定义组件 //非受控的概念:现用现取 class Login extends React.Component{ handleSubmit = (event)=>{ event.preventDefault() console.log(this.user.value); console.log(this.pwd.value); } render(){ return ( <form onSubmit={this.handleSubmit}> 用户名:<input ref={c => this.user = c} type="text" placeholder="用户名"/> 密码:<input ref={c => this.pwd = c} type="password" placeholder="密码"/> <button>登录</button> </form> ) } }
-
-
受控组件
-
//定义组件 //受控的概念:组件中输入类的DOM,随着用户的输入,将输入的值维护到state中 class Login extends React.Component{ state = { username:'', password:'' } //点击登录的回调 handleSubmit = (event)=>{ event.preventDefault() const {username,password} = this.state console.log(username,password); } //存储用户名到状态 saveUsername = (event)=>{ this.setState({username:event.target.value}) } //存储密码到状态 savePwd = (event)=>{ this.setState({password:event.target.value}) } render(){ return ( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={this.saveUsername} type="text" placeholder="用户名"/> 密码:<input onChange={this.savePwd} type="password" placeholder="密码"/> <button>登录</button> </form> ) } }
-
2.7、高阶函数和函数的柯里化
2.7.1、高阶函数
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
- 常见的有:Promise、setTimeout、arr.forEach().....
2.7.2、函数的柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
-
function add(a){ return (b)=>{ return (c)=>{ return a+b+c } } }
2.8、类式组件中的构造器
- 类式组件中的构造器,完全可以省略掉
- 若在类式组件中写了构造器,那就必须调用super
- 调用super时,如果不传props,那么在构造器中,通过this.props是不可以访问props的
-
class Demo extends React.Component{ constructor(props) { super(props) this.state = {isHot:false} console.log('@@@',this.props); } render(){ console.log('#####',this.props); return <h2>你好,我是Demo组件,今天天很{this.state.isHot ? '炎热' : '凉爽'}</h2> } }
2.9、组件的生命周期
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2.9.1、生命周期的三个阶段(旧)
-
初始化阶段: 由ReactDOM.render()触发---初次渲染
-
constructor() ---- 构造器
-
componentWillMount() ---- 组件将要挂载
-
render() ---- 组件初次渲染+更新
-
componentDidMount() ---- 组件挂载完毕
- 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
-
-
更新阶段: 由组件内部this.setSate() 或父组件render触发this.forceUpdate()(备注:强制更新)
- shouldComponentUpdate() ---- 组件更新的“阀门” 注意:强制更新不走“阀门”
- componentWillUpdate() ---- 组件将要更新
- render() ---- 组件初次渲染+更新
- componentDidUpdate() ---- 组件更新完毕
-
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
-
componentWillUnmount() ---- 组件将要卸载(常用)
- 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
-
-
//定义组件 class Sum extends React.Component{ //构造器----调1次 constructor(){ console.log('---constructor---'); super() this.state = {count:0} } //加1的回调 add = ()=>{ const {count} = this.state this.setState({count:count+1}) } //卸载的回调 death = ()=>{ ReactDOM.unmountComponentAtNode( document.getElementById('test') ) } //强制更新的回调 force = ()=>{ this.forceUpdate() } //组件将要挂载---调1次 componentWillMount(){ console.log('---componentWillMount---'); } //组件挂载完毕---调1次 componentDidMount(){ console.log('---componentDidMount---'); } //组件将要卸载---调1次 componentWillUnmount(){ console.log('---componentWillUnmount---'); } //组件更新的“阀门” shouldComponentUpdate(){ console.log('---shouldComponentUpdate---'); return true } //组件将要更新----调用n次,n是更新的次数 componentWillUpdate(){ console.log('---componentWillUpdate---'); } //组件更新完毕----调用n次,n是更新的次数 componentDidUpdate(){ console.log('---componentDidUpdate----'); } componentWillReceiveProps(){ console.log('---componentWillReceiveProps---'); } //组件初次渲染+更新---调1+n次 render(){ console.log('---render---'); return( <div> <h2>当前求和为:{this.state.count},值是{this.props.a}</h2> <button onClick={this.add}>点我+1</button> <button onClick={this.death}>卸载组件</button> <button onClick={this.force}>强制更新一下</button> </div> ) } } //渲染组件 ReactDOM.render(<Car/>,document.getElementById('test'))
2.9.2、生命周期流程图(新)
-
初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
-
更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
-
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
2.9.3、重要的勾子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
2.9.4、即将废弃的勾子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
即将废弃的钩子现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
2.10、虚拟DOM与DOM Diffing算法
经典面试题:
-
React/Vue中的key有什么作用?(key的内部原理是什么?)
虚拟DOM中key的作用:
-
简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
-
详细中说:当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
-
旧虚拟DOM中找到与新虚拟DOM相同的key
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
-
旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到页面
-
-
-
为什么遍历列表时,key最好不要用index?
用index作为key可能引发的问题:
- 若对数据进行:逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
- 如果结构中还包含输入类的DOM,会产生错误的DOM更新 ===> 界面有问题
- 注意!如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index做为key是没有问题的。
-
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
- 如果确定只是简单的展示数据,用index也是可以的。
3、React应用(基于React脚手架)
3.1、使用create-react-app创建react应用
3.1.1、react脚手架
-
xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查、jsx编译、devServer…)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
-
react提供了一个用于创建react项目的脚手架库: create-react-app
-
项目的整体技术架构为: react + webpack + es6 + eslint
-
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
3.1.2、创建项目并启动
- 第一步,全局安装:npm i -g create-react-app
- 第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
- 第三步,进入项目文件夹:cd hello-react
- 第四步,启动项目:npm start
3.1.3、react脚手架项目结构
-
public文件夹 静态资源文件夹 favicon.icon 网站页签图标 index.html 主页面 logo192.png logo图 logo512.png logo图 manifest.json 应用加壳的配置文件 robots.txt 爬虫协议文件 -
src文件夹 源码文件夹 App.css App组件的样式 App.js App组件(App是所有组件的外壳组件,它包着所有的组件) App.test.js 用于给App做测试 index.css 样式 index.js 入口文件 logo.svg logo图 reportWebVitals.js 页面性能分析文件(需要web-vitals库的支持) setupTests.js 组件单元测试的文件(需要jest-dom库的支持)
3.1.4、功能界面的组件化编码流程(通用)
-
拆分组件: 拆分界面,抽取组件
-
实现静态组件: 使用组件实现静态页面效果
-
实现动态组件
-
动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件?
-
交互(从绑定事件监听开始)
-
4、React ajax
- React本身只关注于界面, 并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react应用中需要集成第三方ajax库(或自己封装)
4.1、常用的ajax请求库
-
jQuery: 比较重, 如果需要另外引入不建议使用
-
axios: 轻量级, 建议使用
- 封装XmlHttpRequest对象的ajax
- promise风格
- 可以用在浏览器端和node服务器端
-
Fetch:原生函数,没人用
- 不再使用XmlHttpRequest对象提交ajax请求
- 采用关注分离,老版本浏览器可能不支持
4.2、react中使用axios
4.2.1、文档
4.2.2、相关API(实战案例)
- github搜索案例
-
//定义组件 export default class Search extends Component { search = ()=>{ const {keyWord} = this //使用axios发送请求 axios.get(`http://localhost:3000/search/users?q=${keyWord.value}`).then( response => { console.log('成功了',response.data); }, error => { console.log('失败了',error); } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">Github用户搜索</h3> <div> <input ref={c => this.keyWord = c} type="text" placeholder="请输入用户名"/> <button onClick={this.search}>搜索</button> </div> </section> ) } }
4.3、react脚手架配置代理总结
4.3.1、方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
4.3.2、方法二
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js -
编写setupProxy.js配置具体代理规则:
//新版配置代码 const {createProxyMiddleware} = require('http-proxy-middleware') module.exports = function(app){ app.use( //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) createProxyMiddleware('/api1',{ target:'http://localhost:5000', //控制服务器接收到的请求头中host字段的值 //设置为true时,服务器收到的请求头中的host为:localhost:5000 //设置为false时,服务器收到的请求头中的host为:localhost:3000 //默认值为false,但我们一般将changeOrigin值设为true changeOrigin:true, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) pathRewrite:{'^/api1':''} }), createProxyMiddleware('/api2',{ target:'http://localhost:5001', changeOrigin:true, pathRewrite:{'^/api2':''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
4.4、消息订阅-发布机制
-
工具库: PubSubJS
-
下载: npm install pubsub-js --save
-
使用:
-
import PubSub from 'pubsub-js' //引入 PubSub.subscribe('delete', function(data){ }); //订阅 PubSub.publish('delete', data) //发布消息
-
5、React路由
5.1. 相关理解
5.1.1. SPA的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取, 并在前端异步展现。
5.1.2、路由的理解
1、什么是路由?
- 一个路由就是一个映射关系(key:value)
- key为路径, value可能是function或component
小诀窍:路由路由,根据路径,由我给你展示某个组件
2、路由的分类
后端路由:
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由: router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由:
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由:
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
5.2、react-router-dom路由组件
5.2.1、简介
- react的一个插件库。
- 专门用来实现一个SPA应用。
- 基于react的项目基本都会用到此库。
5.2.2、react-router-dom相关API
-
引入方式,分别引入
import {NavLink,Route,Switch,Redirect} from 'react-router-dom' -
Link ---- 路径的切换
<Link className="list-group-item" to="/about">About</Link> -
Route ---- 注册路由
<Route path="/about" component={About}/> -
NavLink ---- 路径切换,带有高亮效果(active)
//activeClassName ---- 修改选中样式类名 <NavLink activeClassName="demo" className="list-group-item" to="/about">About</NavLink> -
Switch ---- 开启单一匹配,提高效率
<Switch> <Route path="/about" component={About}/> </Switch> -
Redirect ---- 默认路由,一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch> -
BrowserRouter ---- 包裹在组件最外面
ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.getElementById('root')) -
HashRouter ---- 包裹在组件的最外面(路径中加一个#,#后面路径不发送服务器)
5.2.3、路由的基本使用
-
安装react-router-dom库
-
明确好界面中的导航区、展示区,准备好Home组件、About组件
-
导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link> -
示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/> -
的最外侧包裹了一个或
5.3、路由其他相关知识
5.3.1、路由组件与一般组件
-
写法不同
- 一般组件:
- 路由组件:
-
存放位置不同:
- 一般组件:components
- 路由组件:pages
-
接收到的props不同:
-
一般组件:写组件标签时传递了什么,就能收到什么
-
路由组件:接收到三个固定的属性
-
history:
- go: ƒ go(n) ---- 前进或后退指定步数
- goBack: ƒ goBack() ---- 后退
- goForward: ƒ goForward() ---- 前进
- push: ƒ push(path, state) ---- 有痕迹跳转
- replace: ƒ replace(path, state) ---- 无痕跳转
-
location:
- pathname: "/about" ---- 当前路径
- search: ""
- state: null
-
match:
- isExact: true
- params: {}
- path: "/about" ---- 当前路径
- url: "/about" ---- 当前路径
-
-
5.3.2、解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
- 使用HashRouter
5.3.3、路由的严格匹配与模糊匹配
-
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
-
开启严格匹配:
<Route exact={true} path="/about" component={About}/> -
严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
5.3.4、嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
5.3.4、withRouter非路由组件挂载路由属性
withRouter可以在一个非路由组件上添加上路由组件独有的三大属性、history、location、match
-
引入
import {withRouter} from 'react-router-dom' -
正常编写组件
class Header extends Component{ .... } -
组件暴露时,调用withRouter函数加工组件
export default withRouter(Header)
5.4、向路由组件传递参数
5.4.1、params参数
-
路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link> -
注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/> -
接收参数:
this.props.match.params
5.4.2、search参数
-
路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link> -
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/> -
接收参数:
this.props.location.search -
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
5.4.3、state参数
-
路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link> -
注册路由(无需声明,正常注册即可):
-
<Route path="/demo/test" component={Test}/> -
接收参数:
this.props.location.state -
备注:刷新也可以保留住参数
6、redux
6.1、 redux理解
6.1.1、学习文档
- 英文文档: redux.js.org/
- 中文文档: www.redux.org.cn/
- Github: github.com/reactjs/red…
6.1.2、redux是什么
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件共享的状态。
6.1.3、什么情况下需要使用redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
6.1.4、redux工作流程
6.2、redux的三个核心概念
6.2.1、action
-
动作的对象
-
包含2个属性
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
-
例子:
{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
6.2.2、reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的state和action, 产生新的state的纯函数* 。*
6.2.3、store
-
将state、action、reducer联系在一起的对象
-
如何得到此对象?
import {createStore} from 'redux' import reducer from './reducers' const store = createStore(reducer) -
此对象的功能?
getState(): //得到state dispatch(action): //分发action, 触发reducer调用, 产生新的state subscribe(listener): //注册监听, 当产生了新的state时, 自动调用
6.3、redux的核心API
6.3.1、createstore()
作用:创建包含指定reducer的store对象
6.3.2、store对象
-
作用: redux库最核心的管理对象
-
它内部维护着:
- state
- reducer
-
核心方法
getState() dispatch(action) subscribe(listener) -
具体编码:
store.getState() store.dispatch({type:'INCREMENT', number}) store.subscribe(render)
6.3.3、applyMiddleware()
作用:应用上基于redux的中间件(插件库)
6.3.4、combineReducers()
作用:合并多个reducer函数
6.4、实战案例:求和
6.4.1、案例步骤:
-
去除Count组件自身的状态
-
src下建立redux文件夹,文件夹包含一下文件
- store.js ----> redux库最核心的管理对象
- reducer.js ----> 用于初始化状态、加工状态。
- action.js ----> 专门用于创建action对象
- constant.js ----> 放置编码容易疏忽写错action中的type
-
action.js:
-
创建工动作对象,包含两个属性 type 和 data
export function createIncrementAction(number){ return {type:'add',data:number} }
-
-
store.js:
-
引入redux中的createStore函数,创建一个store
//引入createStore,用于创建store对象 import {createStore} from 'redux' //引入为Count组件服务的reducer,用于:初始化状态、加工状态 import countReducer from './count_reducer' -
createStore调用时要传入一个为其服务的reducer
export default createStore(countReducer) -
记得暴露store对象
-
-
reducer.js:
-
reducer的本质是一个函数,接收:preState,action,返回加工后的状态
-
reducer有两个作用:初始化状态,加工状态
-
reducer被第一次调用时,是store自动触发的,传递的preState是undefined
export default function Reducer(preState=0,action){ //从action对象中获取type和data const {type,data} = action switch (type) { case 'add': //如果是加 return preState + data case 'sub': //如果是减 return preState - data default: //如果是初始化 return preState } }
-
-
在项目组件中引入store和action
import store from '../../redux/store' import {addAction,subAction} from '../../redux/count_action' -
在index.js中检测store中状态的改变,一旦发生改变重新渲染
import store from './redux/store' //检测redux中状态的改变 store.subscribe(()=>{ ReactDOM.render(<App/>,document.getElementById('root')) })
6.5、react-redux
- 一个react插件库
- 专门用来简化react应用中使用redux(反而复杂)
6.5.1、react-Redux将所有组件分成两大类
-
UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
-
容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
6.5.2、相关API
-
Provider:让所有组件都可以得到state数据
<Provider store={store}> <App /> </Provider> -
connect:用于包装 UI 组件生成容器组件
import { connect } from 'react-redux' connect( mapStateToprops, mapDispatchToProps )(Counter) -
mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) { return { value: state } } -
mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
6.6、使用上redux调试工具
6.6.1、 安装chrome浏览器插件
- Reduo Dev Tools
6.6.2、下载工具依赖包
-
npm install --save-dev redux-devtools-extension
6.7、纯函数和高阶函数
6.7.1. 纯函数
-
一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
-
必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
-
redux的reducer函数必须是一个纯函数
7.8.2. 高阶函数
-
理解: 一类特别的函数
- 情况1: 参数是函数
- 情况2: 返回是函数
-
常见的高阶函数:
- 定时器设置函数
- 数组的forEach()/map()/filter()/reduce()/find()/bind()
- promise
- react-redux中的connect函数
-
作用: 能实现更加动态, 更加可扩展的功能