React可以克服原生JS的以下缺点:
react开发者工具:Chrome插件 React Developer Tool(注意安装来源为facebook的)
全称: JavaScript XML,是react定义的一种类似于XML的JS扩展语法,本质是React.createElement(component, props, ...children)
方法的语法糖。JXS最终产生的虚拟DOM就是一个JS对象。
更加简单地创建虚拟DOM
(1)使用JSX创建虚拟DOM
(2)使用JS创建虚拟DOM(用原生JS,不用babel,开发中不使用)
JSX创建虚拟DOM的方法是JS方法的语法糖
定义虚拟DOM时不用写引号
标签中混入JS表达式时要用{}
const VDOM=(
遇到以 { 开头的代码,以JS语法解析,且标签中的js表达式必须用{ }包含,比如
中的{}
中的{index}
和{item}
注:key={index}
:在进行组件遍历的时候必须要加一个key来区分每个组件
class
,用className
style={{key:value}}
,如style={{color:‘white’,fontSize:20px}}
辨析【js表达式】和【js语句 (代码)】
a
a+b
demo(1)
arr.map()
function test () {}
if () {}
for () {}
switch () {case:xxxx}
适用于简单组件(无state
)
注意事项:
this
指向undefined
,因为babel编译后开启了严格模式本例中,执行了ReactDOM.render(
之后,发生了什么?
适用于复杂组件(有state
)
注意事项:
React.Component
render
函数,且要有返回值render
是放在类的原型对象上,供实例使用。render
中的this指向类的实例对象(原因见下方) <=> 类式组件的实例对象。本例中,执行了ReactDOM.render(
之后,发生了什么?
new
出来该类的实例,并通过该实例调用到原型上的render
方法。render
返回的虚拟DOM转为真实DOM,随后呈现在页面中。针对于类式组件,hooks可以让函数式组件也有三大属性
state
是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)state
来更新对应的页面显示(重新渲染组件)代码实例: 点击切换天气状态
标准写法
注意事项:
constructor
中初始化state
,且要用对象形式初始化state
render
函数中创建虚拟DOM时,直接在标签中绑定事件,且事件写法不同于原生JS,如原生JS中的onclick
事件,在react中要写成onClick
,其他同理。onClick={this.changeWeather}
是将this.changeWeather
函数赋值给onClick
,函数后面不能加括号,否则就是将函数返回值赋值changeWeather
是作为onClick
的回调,所以不是通过实例调用的,是直接调用,且类中的方法默认开启了局部的严格模式,所以其中this
的指向不是实例对象,而是undefined
。render
函数也是放在对象的原型链上,但是它是由类的实例对象调用的,所以this
指向实例对象this
可以在构造器中用bind
更改指向,生成的新函数直接在类中,所以this
指向实例对象const {isHot,wind} = this.state
是ES6中的对象解构,获取多个对象属性的方法简写方法
注意事项:
({18}代表是数值型,“18”代表是字符串)。props
是对象,里面存储着键值对形式,name="18"
中,name
是键,"18"
是值
。这里…不是三点(拓展/展开运算符),因为对象不能使用拓展运算符(数组和可遍历的伪数组可以)。而{...obj}
是ES6语法,是一个复制对象。但是在这里,{...p}
并不是复制对象,因为这里的{}
表示括号里面要写js表达式了,所以真正写的还是...p
,这里react+babel就允许用展开运算符展开对象,不能随便使用(不能用console.log()
查看),仅仅适用于标签传递数据时。props
属性中,在render
中可以通过this.props
获取数据this.props.属性名
constructor
可省。在 react 组件挂载之前,会调用它的构造函数。在为
React.Component
子类实现构造函数时,应在其他语句之前调用super(props)
。否则,this.props 在构造函数中可能会出现未定义的 bug。
通常,在 react 中,构造函数仅用于以下两种情况: a)通过给
this.state
赋值对象来初始化内部state
。
b)为事件处理函数绑定实例
对props进行限制
首先要引入prop-types库,用于对组件标签属性进行限制
限制props
有两种方法:
a)限制内容写在类外面
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
b)限制内容写在类里面
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
注意事项:
propTypes
是类里的属性规则PropTypes
是prop-types库里的内置对象name: React.PropTypes.string.isRequired
函数组件使用props
三大属性中,只有props
可以用于函数组件,因为函数可以接收参数,state
和refs
都不能用于函数组件。
注意事项:
props
只能使用第一种方法组件内的标签可以定义ref
属性来标识自己。
this.refs
拿到真实DOM
字符串形式的ref
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
console.log(this);
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
console.log(this);
const {input2} = this.refs
alert(input2.value)
}
render(){
return(
)
}
}
//渲染组件到页面
ReactDOM.render( ,document.getElementById('test'))
不被官方推荐,因为效率不高
回调ref
1. 内联函数(推荐)
this.input1 = currentNode } type="text" placeholder="点击按钮提示数据"/>
{/*这里的this是指向实例对象,因为箭头函数没有指向,查找外侧的this指向*/}
注意:
currentNode
是ref
所在的节点input1
是类的属性,即直接绑定到类里,而不是像字符串ref一样添加到refs
对象里回调函数:有定义、没有执行(函数名())、最终执行(别人调用)
2. 类绑定函数
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
3. 回调ref中回调执行次数
内联函数更新时会执行两次,一次清空,一次执行函数,类绑定函数不会。
交互和更改状态的区别:取决于是否修改render
函数中节点的内容
createRef(react最推荐)
React.createRef
调用后可以返回一个容器,该容器可以存储被ref
所标识的节点,该容器是“专人专用”的,有多少个节点表示ref
,就要调用多少次 React.createRef
onXxx
属性指定事件处理函数(注意大小写)event.target
得到发生事件的DOM元素对象。发生事件的元素就是操作的元素则可以省略ref。ref
。以表单提交案例为例
用ref
实现
页面中所有的输入类DOM现用现取,即通过ref
标识DOM,进而获取数据
知识点:
中都有onSubmit
属性来控制提交之后的状态
)得有name
属性才能通过GET请求获取到query参数(用?携带)action
无法阻止表单页面刷新以及地址栏更新,得要禁止默认事件event.preventDefault()
的默认type
属性值就是submit
用onChange
+state
实现
页面中所有的输入类DOM将数据存在state
中
更推荐用受控组件,减少ref
的使用
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
Promise
、setTimeout
、arr.map()
等等函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
用函数的柯里化实现受控组件
为了不重复编写相似的代码,如saveUsername
和savePassword
this.saveFormData('username')
,有了小括号,立即调用,但是返回的还是一个函数,符合回调函数的要求
[dataType]
:调用变量形式的对象属性名
event
形参不需要实参,可以直接调用,所以event不能写进this.saveFormData('username')
的参数中,得用柯里化形式来体现
不用函数柯里化的实现方式
只需改两处:
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
用户名: this.saveFormData(‘username’,event) } type=“text” name=“username”/>
两种方法都常用
ReactDOM.render( , document.querySelector('test'))
ReactDOM.unmountComponentAtNode(document.querySelector('test'))
知识点:
style={{opacity:opacity}}
;{opacity:opacity}
简写为{opacity}
render
执行次数:1+n(初始化渲染+更新状态)
三条线:
render
–>componentWillReceiveProps
–>shouldComponentUpdate
–>componentWillUpdate
–>render
–>componentDidUpdate
–>componentWillUnmount
setState()
–>shouldComponentUpdate
–>componentWillUpdate
–>render
–>componentDidUpdate
–>componentWillUnmount
forceUpdate()
–>componentWillUpdate
–>render
–>componentDidUpdate
–>componentWillUnmount
知识点:
shouldComponentUpdate
返回值必须为true
或false
,若不写,默认为true
forceUpdate()
:强制更新。不更改任何状态中的数据,强制更新一下componentWillReceiveProps
:第一次调用不算。生命周期的三个阶段(旧)
ReactDOM.render()
触发—初次渲染constructor()
componentWillMount()
render()
: 常用,一定得调用componentDidMount()
: 常用,一般在这个钩子中做一些初始化的事,如开启定时器、发送网络请求、订阅消息this.setSate()
或父组件重新render
触发shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
ReactDOM.unmountComponentAtNode()
触发componentWillUnmount()
:常用,一般在这个钩子中做一些收尾的事,如关闭定时器、取消订阅消息componentWillMount
、componentWillUpdate
、componentWillReceiveProps
三个钩子使用前要加UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。getDerivedStateFromProps
和getSnapshotBeforeUpdate
static getDerivedStateFromProps
:适用于罕见用例(几乎不用),返回null
或state对象,state的值在任何时候都取决于props
getSnapshotBeforeUpdate(prevProps,prevState,)
:在更新之前获取快照,返回值传递给componentDidUpdate(prevProps,prevState,snapshotValue)
DOM Diffing算法对比的最小粒度是标签,且逐层对比
经典面试题:
解释:
简单地说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
详细地说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1) 若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2) 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key:
根据数据创建新的真实DOM,随后渲染到到页面
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
如果结构中还包含输入类的DOM(如input,虚拟DOM中标签属性少,input没有value属性):
会产生错误DOM更新 ==> 界面有问题。
注意! 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
使用脚手架开发的项目:模块化、组件化、工程化
安装cnpm: npm install -g cnpm --registry=https://registry.npm.taobao.org
安装yarn: 不推荐用npm i -g yarn 安装(但我是这样装的)
react扩展程序: React Developer Tools(facebook)
问题:
全局安装react脚手架: npm i -g create-react-app (不成功),得用 cnpm i -g create-react-app
创建项目: 切换到想创建项目的文件夹,使用命令create-react-app app_name
换源: 在这里建议把npm换源,使用国内镜像创建项目。因为使用脚手架配置项目的时候,需要下载的依赖实在太多,不换源的话很可能中途卡住就失败了。(没换)
换源方式: npm config set registry https://registry.npm.taobao.org
验证是否换源成功:npm config get registry
显示出上述地址的话就是换源成功了
npm cnpm npx nvm区别
知识点:
注意:
组件js文件除了首字母大写,还可以将后缀改为jsx,来和普通js文件进行区分
import name from './name.module.css'
className={name.title}
基本上不用,而是用less嵌套
遇到的问题:
删除引入网站页签图标的代码后,页签还是在???
因为浏览器缓存了之前的图标,清空浏览器缓存之后,就正常了(并不是)
win10系统下在cmd中 输入npm start后,项目能正常跑起来,但是整个cmd窗口就不能再输入任何东西
ctrl+c 可以停掉
安装插件 ES7+ React/Redux/React-Native snippets
APP给list用props传递参数:
,这里是以键值对的形式传递数组,键是第一个todos
,数组是第二个todos
。其中第二个todos
(数组)为const {todos}=this.state
,todos
是从state
中解构出来的由对象组成的数组。
List给Item用props传递参数:
todos.map(todo=>{
return
})
其中 {todos}=this.props
,是 todos
解构对象成数组,即 todos=[{},{}],todo代表todos数组中的每个元素(对象)。
{...todo}
是在展开对象。原生js中,对象不能使用拓展运算符(数组和可遍历的伪数组可以)。而{...obj}
是ES6语法,是一个复制对象。但是在这里,{...todo}
并不是复制对象,因为这里的{}
表示括号里面要写js表达式了,所以真正写的还是...todo
,这里react+babel就允许用展开运算符展开对象,不能随便使用(不能用console.log()
查看),仅仅适用于标签传递数据时。
父组件向子组件传数据:直接在子组件标签中添加键值对形式的数据(props
)
子组件向父组件传递数据:父组件通过props给子组件传递一个函数,子组件在想要传递数据给父组件时,调用该函数
生成时间戳:Date.now()
UUID:生成唯一时间戳的库
npm i uuid (库比较大)
npm i nanoid(库小,很快安装)/ yarn add nanoid
用法:
import {nanoid} from ‘nanoid’
//调用
nanoid()
str.trim()
去除字符串的头尾空格
onMouseLeave={this.handleMouse(false)}
:小括号调用了函数,定义函数时要柯里化
状态在哪里,操作状态的方法就在哪里
window.confirm("确定删除吗")
:返回true或false
在react中,若加了
checked
属性,则必须加onChange
事件,不然无法改变勾选状态。defaultChecked
只在第一次起效果。
className
、style
的写法state
中?state
中state
中(官方称此操作为:状态提升)defaultChecked
和 checked
的区别,类似的还有:defaultValue
和 value
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
方法二
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js(不允许改名)
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀/api1,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
//设置响应头 设置允许跨域
response.setHeader('Access-Control-Allow-Origin', '*');
//响应头
response.setHeader('Access-Control-Allow-Headers', '*');
为CORS解决方案
注意:127.0.0.1和localhost的区别
127.0.0.1 为保留地址,直白地说 127.0.0.1 就是一个 ip 地址,不同于其它 ip 地址的是它是一个指向本机的 ip 地址,也称为环回地址,该 ip 地址不能使用在公网 ip 上,对任何一台电脑来说,不管是否连接到INTERNET上,127.0.0.1 都是指向自己。
事实上整个 127.* 网段都是环回地址,127.* ip 段都为保留地址,只是规则制定者将其中的 127.0.0.1 规定为指向本机自己。
在电脑网络中,localhost 为“本地主机”,是给回路网络接口(loopback)的一个标准主机名,IPv4 相对应的地址为 127.0.0.1,IPv6 相对应的地址为 [::1],这个名称也是一个保留域名。
说白了就是 localhost 就是一个本地域名,不是地址。该本地域名指向的 ip 地址就是 127.0.0.1 ,也就是指向本机,localhost 更方便记忆与输入,因 hosts文件 定义了 localhost = 127.0.0.1 ,所以你只需要记住 localhost 就可以代表本机了。
对机器来说,它需要通过 hosts 文件来定义 localhost = 127.0.0.1(多一次解析 ip 的步骤) ,才能知道 localhost 代表的是本机,机器只知道 127.0.0.1 代表本机,因此当你向 localhost 发消息的时候,机器会自动翻译后给127.0.0.1发消息。
在实际工作中,localhost 是不经过网卡传输的,所以它不受网络防火墙和与网卡相关的种种限制,而 127.0.0.1 则要通过网卡传输数据,是必须依赖网卡的。这一点也是 localhost 和 127.0.0.1 的最大的区别,这就是为什么有时候用 localhost 可以访问,但用 127.0.0.1 就不可以的情况。
连续解构赋值:
const {keyWordElement:{value}} = this
相当于 this.keyWordElement.value
,此时keyWordElement
并没有被解构,只是写的过程,即
console.log(keyWordElement) // undefined
const {keyWordElement:{value:keyWord}} = this
连续解构赋值+重命名
状态中的数据驱动着页面的展示
连续写三元表达式
isFirst ?
如果第一个判断为正确,就不会执行后面的代码了,所以顺序很重要
使用:
注意:
4. 适用于任意组件之前的消息沟通
5. 谁用谁接,谁传谁发
设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
ES6小知识点:解构赋值+重命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
消息订阅与发布机制
1.先订阅,再发布(理解:有一种隔空对话的感觉)
2.适用于任意组件间通信
3.要在组件的componentWillUnmount中取消订阅
fetch发送请求(关注分离的设计思想)
try {
const response= await fetch(`/api1/search/users2?q=${keyWord}`)
const data = await response.json()
console.log(data);
} catch (error) {
console.log('请求出错',error);
}
**注意:**react-router总共有三种形式的库,适用于三种场景:
明确好界面中的导航区、展示区
导航区的a标签改为Link标签
Demo
展示区写Route标签进行路径的匹配(在呈现路由组件内容的位置注册路由)
以前的版本
现在的版本 6.x.x
}/>
}/>
的最外侧包裹了一个
或
注意:
import {Link,Route} from 'react-router-dom'
分别暴露,用哪个组件取哪个组件 -
地址栏中#后面的内容不会作为资源发给服务器写法不同:
一般组件:
路由组件:
存放位置不同:
一般组件:components
路由组件:pages
接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: ? go(n)
goBack: ? goBack()
goForward: ? goForward()
push: ? push(path, state)
replace: ? replace(path, state)
location:
pathname: “/about”
search: “”
state: undefined
match:
params: {}
path: “/about”
url: “/about”
NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
标签体内容是一个特殊的标签属性,可以通过this.props.children获取。因此以下两段代码是等价的。
About
什么时候样式丢失?
路由路径多级,且刷新的时候
解决办法
1)public/index.html 中 引入样式时不写 ./ 而是写 / (常用)。因为./是相对路径,去掉之后就是绝对路径,直接去localhost:3000下调文件
2)public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)。因为%PUBLIC_URL%代表public的绝对路径
3)将
改为
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
Home //模糊匹配
开启严格匹配:
// exact={true}可以简写为exact严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
具体编码:
注册子路由时要写上父路由的path值
{/* 注册路由 */}
路由的匹配是按照注册路由的顺序进行的
路由链接(携带参数):
路由链接(携带参数):
路由链接(携带参数):
详情注册路由(无需声明,正常注册即可):
接收参数:this.props.location.state
备注:刷新也可以保留住参数,但回退不行,无法回退到上一个·同级路由**????**(老师演示的时候可以回退,自己跑代码回退不了)
标签中加入replace={true}
或replace
借助this.prosp.history对象上的API对操作路由跳转、前进、后退,而不用路由的和
,但还是要注册路由
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter
的返回值是一个新组件。
在一般组件中要用到路由组件的props属性时引入。
import {withRouter} from 'react-router-dom'
需要暴露
export default withRouter(Header)
material-ui(国外)
ant-design(国内蚂蚁金服)
安装依赖:npm install antd
引入样式(不太合理,引入了全部组件的样式,应该按需引入)
import '../node_modules/antd/dist/antd.css'
引入库:
import { Button,DatePicker } from 'antd';
import {WechatOutlined,WeiboOutlined,SearchOutlined} from '@ant-design/icons'
学会查看官网文档
其他UI组件库:element ui、vant等
重点: 学会查看文档(3.x文档更清楚,且适用于4.x)
安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
修改package.json
“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”,
“eject”: “react-scripts eject”
},
根目录下创建config-overrides.js
//配置具体的修改规则
const { override, fixBabelImports,addLessLoader} = require(‘customize-cra’);
module.exports = override(
fixBabelImports(‘import’, {
libraryName: ‘antd’,
libraryDirectory: ‘es’,
style: true,
}),
addLessLoader({
lessOptions:{
javascriptEnabled: true,
modifyVars: { ‘@primary-color’: ‘green’ },
}
}),
);
备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'
应该删掉
dispatch(action)
是一个函数,action
是一个对象type
:标识属性, 值为字符串, 唯一, 必要属性data
:数据属性, 值类型任意, 可选属性{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
纯函数
。将state、action、reducer联系在一起的对象
如何得到此对象
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
此对象的功能
getState()
: 得到statedispatch(action)
: 分发action, 触发reducer调用, 产生新的statesubscribe(listener)
: 注册监听, 当产生了新的state时, 自动调用作用:创建包含指定reducer的store对象
作用: redux库最核心的管理对象
它内部维护着:
核心方法:
getState()
dispatch(action)
subscribe(listener)
:store监测redux里的状态变化,发生变化则会被调用具体编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
放在入口文件index.js中
store.subscribe(()=>{
ReactDOM.render(,document.getElementById(‘root’))
})
作用:应用上基于redux的中间件(插件库)
作用:合并多个reducer函数
去除Count组件自身的状态
src下建立:
//文件
-redux
-store.js
-count_reducer.js
store.js:
1)引入redux中的createStore
函数,创建一个store
2)createStore
调用时要传入一个为其服务的reducer
3)记得暴露store对象(store.js默认暴露一个函数调用,函数返回一个对象,其他文件引入的时候将此对象命名为store)
count_reducer.js:
1)reducer的本质是一个函数,接收:preState,action,返回加工后的状态
2)reducer有两个作用:初始化状态,加工状态
3)reducer被第一次调用时,是store自动触发的。传递的preState
是undefined
,传递的action
是:{type:'@@REDUX/INIT_a.2.b.4}
在index.js中监测store中状态的改变,一旦发生改变重新渲染
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
新增文件:
count_action.js 专门用于创建action
对象
注意:箭头函数返回一个对象时,要用()
包裹,不然会被识别为函数体的{}
createIncrementAction = data => ({type:INCREMENT,data})
constant.js 放置容易写错的type
值,目的只有一个:便于管理的同时防止程序员单词写错(要暴露)
action
:action
的值是对象action
:action
的值是函数。异步action
一般会调用同步action
action
action
:想要对状态进行操作,但是具体的数据靠异步任务返回。action
的函数不再返回一般对象,而是一个函数,该函数中写异步任务。action
去真正操作数据。action
不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action
。明确两个概念:
1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
2)容器组件:负责和redux通信,将结果交给UI组件。
如何创建一个容器组件—靠react-redux 的connect
函数:
connect(mapStateToProps,mapDispatchToProps)(UI组件)
1)mapStateToProps
:映射状态,返回值是一个对象。返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
2)mapDispatchToProps
:映射操作状态的方法,返回值是一个对象。返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
备注:容器组件中的store是靠props
传进去的,而不是在容器组件中直接引入
容器组件和UI组件整合一个文件
若有多个容器组件,无需自己给每个容器组件传递store,给包裹一个
即可。
使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
mapDispatchToProps
也可以简单的写成一个对象,因为react-redux可以自动dispatch
一个组件要和react-redux“打交道”要经过哪几步?
1)定义好UI组件—不暴露
2)引入connect
生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
3)在UI组件中通过this.props.xxxxxxx
读取和操作状态
combineReducers
进行合并。合并后的总状态是一个对象!!!Redux DevTools
引入库
yarn add redux-devtools-extension
在store.js中进行配置
import {composeWithDevTools} from ‘redux-devtools-extension’
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
打包
npm run build
生成build文件夹
部署在服务器上
1)阿里云线上服务器
2)express(Node中快速搭建服务器的框架)搭建服务器
3)借助第三方库库快速搭建:以指定的文件夹作为根目录快速开启一台服务器。如 serve
npm i serve -g //全局安装
serve build // 打开生成的build文件夹
setState更新状态的2种写法
setState(stateChange, [callback])
------对象式的setState
1)stateChange
为状态改变对象(该对象可以体现出状态的更改)
2)callback
是可选的回调函数, 它在状态更新完毕、界面也更新后(render
调用后)才被调用
setState(updater, [callback])
------函数式的setState
1)updater
为返回stateChange
对象的函数。
2)updater
可以接收到state
和props
。
3)callback
是可选的回调函数, 它在状态更新、界面也更新后(render
调用后)才被调用。
总结:
setState
是函数式的setState
的简写方式(语法糖)setState()
执行后获取最新的状态数据,callback
函数中读取通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
通过指定在加载得到路由打包文件前显示一个自定义loading界面
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login')) //函数体不能加花括号
//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
loading.....}>
React.useState()
React.useEffect()
React.useRef()
State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
语法:
const [xxx, setXxx] = React.useState(initValue)
useState()
说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
setXxx()
的2种写法:
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM(尽量避免)
语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
语法:
const refContainer = useRef() //声明
refContainer.current.xxx //调用
作用: 保存标签对象,功能与React.createRef()
一样
使用
import {Fragment } from 'react'
//类似于一个
//可以添加一个属性 'key',用于遍历
<>> //不能添加任何属性
- 1
- 2
- 3
- 4
作用:可以不用必须有一个真实的DOM根标签了
8.5 Context
8.5.1 理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
8.5.2 使用
-
创建Context容器对象:
const XxxContext = React.createContext()
//const {Provider,Consumer} = XxxContext 也可以结构赋值
- 1
- 2
-
渲染子组时,外面包裹xxxContext.Provider
, 通过value
属性给后代组件传递数据:
子组件
- 1
- 2
- 3
-
后代组件读取数据:
1) 第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
- 1
- 2
2)第二种方式: 函数组件与类组件都可以
//组件中的函数
{
value => ( // value就是context中的value数据
要显示的内容
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注意: 在应用开发中一般不用context, 一般都用它的封装react插件(如,react-redux)
8.6 组件优化
Component的2个问题
-
只要执行setState()
,即使不改变状态数据, 组件也会重新render() ==> 效率低
-
只当前组件重新render()
, 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state
或props
数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()
总是返回true
解决
-
办法1: 重写shouldComponentUpdate()
方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
shouldComponentUpdate(nextProps,nextState){
// console.log(this.props,this.state); //目前的props和state
// console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.state.carName === nextState.carName
}
- 1
- 2
- 3
- 4
- 5
-
办法2: 使用PureComponent
PureComponent
重写了shouldComponentUpdate()
, 只有state或props数据有变化才返回true
import React, { PureComponent } from 'react'
//之前是import React, { Component } from 'react'
- 1
- 2
注意:
1. PureComponent
,只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
2. 不要直接修改state数据, 而是要产生新数据(新对象)。所以不能用unshift
5. 项目中一般使用PureComponent
来优化
8.7 render props
8.7.1 如何向组件内部动态传入带内容的结构(标签)
-
Vue中:
使用slot(插槽)技术, 也就是通过组件标签体传入结构
- 1
-
React中:
使用children props:通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
8.7.2 children props
{this.props.children}
- 1
- 2
- 3
- 4
问题: 如果B组件需要A组件内的数据, ==> 做不到
8.7.3 render props
} />
//相当于 }>
- 1
- 2
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
注意: 只要组件不带内容,单标签和双标签实现效果相同(即
==
)
8.8 错误边界
8.8.1 理解
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
8.8.2 特点
- 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
- 只适用于生产环境(build打包),而不是开发环境
8.8.3 使用方式
getDerivedStateFromError
配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
8.9 组件通信方式总结
8.9.1 组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
8.9.2 几种通信方式:
- props:
1)children props
2)render props - 消息订阅-发布:
pubs-sub、event等等 - 集中式管理:
redux、dva等等 - conText:
生产者-消费者模式
8.9.2 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
第9章 React Router 6 快速上手
9.1 概述
-
React Router 以三个不同的包发布到 npm 上,它们分别为:
1)react-router: 路由的核心库,提供了很多的:组件、钩子。
2) react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如
等 。
3) react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:
等。
-
与React Router 5.x 版本相比,改变了什么?
1) 内置组件的变化:移除
,新增
等。
2) 语法的变化:component={About}
变为 element={ }
等。
3).新增多个hook:useParams
、useNavigate
、useMatch
等。
4) 官方明确推荐函数式组件了!!!
9.2 Component
1.
-
说明:
用于包裹整个应用。
-
示例代码:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
{/* 整体结构(通常为App组件) */}
,root
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.
- 说明:作用与
一样,但
修改的是地址栏的hash值。 - 备注:6.x版本中
、
的用法与 5.x 相同。
3. 与
-
v6版本中移出了先前的
,引入了新的替代者:
。
-
和
要配合使用,且必须要用
包裹
。
-
相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
-
属性用于指定:匹配时是否区分大小写(默认为 false)。
-
当URL发生变化时,
都会查看其所有子
元素以找到最佳匹配并呈现组件 。
-
也可以嵌套使用,且可配合useRoutes()
配置 “路由表” ,但需要通过
组件来渲染其子路由。
-
示例代码:
/*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/
}>
/*用于定义嵌套路由,home是一级路由,对应的路径/home*/
}>
/*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/
}>
}>
//Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx
} />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
4.
-
作用: 修改URL,且不发送网络请求(路由链接)。
-
注意: 外侧需要用
或
包裹。
-
示例代码:
import { Link } from "react-router-dom";
function Test() {
return (
按钮
);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
5.
-
作用: 与
组件类似,且可实现导航的“高亮”效果。
-
示例代码:
// 注意: NavLink默认类名是active,下面是指定自定义的className
//自定义样式
{
console.log('home', isActive)
return isActive ? 'base one' : 'base'
}}
>login
/*
默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
home
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
注意: 默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
6.
-
作用:只要
组件被渲染,就会修改路径,切换视图。
-
replace
属性用于控制跳转模式(push 或 replace,默认是push)。
-
示例代码:
import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'
export default function Home() {
const [sum,setSum] = useState(1)
return (
我是Home的内容
{/* 根据sum的值决定是否切换视图 */}
{sum === 1 ? sum的值为{sum}
: }
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
7.
-
当
产生嵌套时,渲染其对应的后续子路由。
-
示例代码:
//根据路由表生成对应的路由规则
const element = useRoutes([
{
path:'/about',
element:
},
{
path:'/home',
element: ,
children:[
{
path:'news',
element:
},
{
path:'message',
element: ,
}
]
}
])
//Home.js
import React from 'react'
import {NavLink,Outlet} from 'react-router-dom'
export default function Home() {
return (
Home组件内容
-
News
-
Message
{/* 指定路由组件呈现的位置 */}
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
9.3 Hooks
1-6常用
1. useRoutes()
-
作用:根据路由表,动态创建
和
。
-
示例代码:
//路由表配置:src/routes/index.js
import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'
export default [
{
path:'/about',
element:
children:[ //子路由
{
path:'news',
element:
}
]
},
{
path:'/home',
element:
},
{
path:'/',
element:
}
]
//App.jsx
import React from 'react'
import {NavLink,useRoutes} from 'react-router-dom'
import routes from './routes'
export default function App() {
//根据路由表生成对应的路由规则
const element = useRoutes(routes) //一级路由用useRoutes()和element定位,子路由用 定位
return (
......
{/* 注册路由 */}
{element}
......
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
注意: 一级路由(App.js中)用useRoutes()和element定位,子路由用
定位
2. useNavigate()
-
作用:返回一个函数用来实现编程式导航。
-
示例代码:
import React from 'react'
import {useNavigate} from 'react-router-dom'
export default function Demo() {
const navigate = useNavigate()
const handle = () => {
//第一种使用方式:指定具体的路径
navigate('/login', {
replace: false,
state: {a:1, b:2}
})
//第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
navigate(-1)
}
return (
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
3. useParams()
-
作用:回当前匹配路由的params
参数,类似于5.x中的match.params
。
-
示例代码:
import React from 'react';
import { Routes, Route, useParams } from 'react-router-dom';
import User from './pages/User.jsx'
function ProfilePage() {
// 获取URL中携带过来的params参数
let { id } = useParams();
}
function App() {
return (
}/>
);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
4. useSearchParams(search,setSearch)
-
作用:用于读取和修改当前位置的 URL 中的查询字符串。
-
返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
-
示例代码:
import React from 'react'
import {useSearchParams} from 'react-router-dom'
export default function Detail() {
const [search,setSearch] = useSearchParams()
const id = search.get('id')
const title = search.get('title')
const content = search.get('content')
return (
-
- 消息编号:{id}
- 消息标题:{title}
- 消息内容:{content}
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
5. useLocation()
-
作用:获取当前 location 信息,对标5.x中的路由组件的location
属性。可以传递state参数
-
示例代码:
import React from 'react'
import {useLocation} from 'react-router-dom'
export default function Detail() {
const {state:{id,title,content}} = useLocation()
//const x = useLocation()
//console.log('@',x)
// x就是location对象:
/*
{
hash: "",
key: "ah9nv6sz",
pathname: "/login",
search: "?name=zs&age=18",
state: {a: 1, b: 2}
}
*/
return (
- 消息编号:{id}
- 消息标题:{title}
- 消息内容:{content}
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
6. useMatch()
-
作用:返回当前匹配信息,对标5.x中的路由组件的match
属性。
-
示例代码:
}/>
登录
export default function Login() {
const match = useMatch('/login/:x/:y')
console.log(match) //输出match对象
//match对象内容如下:
/*
{
params: {x: '1', y: '10'}
pathname: "/LoGin/1/10"
pathnameBase: "/LoGin/1/10"
pattern: {
path: '/login/:x/:y',
caseSensitive: false,
end: false
}
}
*/
return (
Login
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
7. useInRouterContext()
作用:如果组件在
的上下文中呈现,则 useInRouterContext
钩子返回 true,否则返回 false。
8. useNavigationType()
- 作用:返回当前的导航类型(用户是如何来到当前页面的)。
- 返回值:
POP
、PUSH
、REPLACE
。 - 备注:
POP
是指在浏览器中直接打开了这个路由组件(刷新页面)。
9. useOutlet()
-
作用:用来呈现当前组件中渲染的嵌套路由。
-
示例代码:
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象
- 1
- 2
- 3
- 4
10.useResolvedPath()
- 作用:给定一个 URL值,解析其中的:path、search、hash值。