# react学习笔记
**Repository Path**: davi2016/react-learning-notes
## Basic Information
- **Project Name**: react学习笔记
- **Description**: 是在react学习阶段写的笔记和一些代码,还在完善当中
- **Primary Language**: JavaScript
- **License**: AFL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2022-10-16
- **Last Updated**: 2022-10-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 一、React基础(不使用脚手架)
## 1.导入包依赖
核心库:`react.development.js`
扩展库:`react-dom.development.js`
解析库:`babel.min.js`
## 2.使用方法
```html
Document
```
## 3.`jsx`规则
* 定义虚拟`dom`用()
* 标签中混入`js`语法要用{}
* 样式类名要用`className`
* 可以使用style来改变外观,假如直接定义样式则用{{}},如果引用`createStyle`中的对象,则直接用{}
* 根标签只能有一个
* 标签必须闭合
* 标签首字母
* 若小写标签首字母则将标签转义为html标签,如果html标签中不存在该标签,则报错
* 大写字母开头标签是去寻找react对应的组件
## 4.函数式组件(适用于简单组件)
> 使用方式
>
> ```jsx
> function MyComponent() {
> console.log( this )//此处的this是undefined,因为babel开启了严格模式,严格模式下函数内部this不指向windows
> return (
> this is my component
> )
> }
> ```
## 5.类组件(适用于复杂组件)
> 使用方式
>
> ```jsx
> class MyComponent extends React.Component{
> constructor( props ){
> super( props );
> }
> render(){
> return (
>
>
this is my class component
>
> )
> }
> }
> ```
## 6.类组件和函数式组件的区别
> * 类组件适用复杂组件,函数式组件适用于简单组件
> * 有状态的组件为复杂组件,否则为简单组件
## 7.类组件中需要绑定事件时注意this指向
> ```jsx
> class MyComponent extends React.Component{
>
> constructor( props ){
> super( props );
> //此处将myClick中的this指向MyComponent
> this.myClick = this.myClick.bind(this);
> this.state = {
> weather: 'isHot'
> }
> }
> render(){
> return (
>
>
> //只是将myclick的引用交给了onClick
>
this is my class component
> { this.state.weather }
>
> )
> }
>
> myClick(){
> let weather = this.state.weather === 'isCold'? 'isHot' : 'isCold'
> this.setState({
> weather
> });
> }
> }
>
> let app = (
>
>
>
> )
>
> ReactDOM.render( app, document.getElementById('box') );
> ```
>
> this指向问题并非react中的错误,而是`es6`语法class中的问题:绑定事件是只是将堆中的函数赋值给事件名(如:onClick),并没有调用,当点击时调用的实际时onClick的回调,而在class中定义函数会开启局部严格模式,将this指向undefined,所以假如在构造函数中不绑定this指向,会报错。
## 8.state和方法绑定的简写形式
```jsx
class MyComponent extends React.Component{
//自定义属性
state = {
weather: 'isHot'
}
render(){
return (
this is my class component
{ this.state.weather }
)
}
//自定义方法
myClick = () => {
let weather = this.state.weather === 'isCold'? 'isHot' : 'isCold'
this.setState({
weather
});
}
}
```
在类中可以直接给变量赋值且变量在实例身上,因此state可以直接放在构造方法外面。让绑定的事件以变量形式存储函数,且变量名挂在实例对象身上,使用箭头函数,this指向直接指向实例对象本身。
## 9.`props`
### (1)基础用法
> ```jsx
> class MyComponent extends React.Component{
> render(){
> const { name, age, grade } = this.props;
> return (
>
> - {name}
> - {age}
> - {grade}
>
> )
> }
> }
>
> const obj = {
> name: '帅哥',
> age: 22,
> grade: 80
> }
>
> let app = (
>
>
>
> //批量的传递标签属性
> //简写方式 //等效于上面的展开一项一项传递
>
> )
> ```
>
> **...三点展开语法不可展开对象,只能展开数组。{ ...obj}是可以进行一个对象的深拷贝,但是此处标签中使用的并不是深拷贝,而是babel和react配合使用时提供的一种可以对对象的展开语法,只可以在标签身上使用。**
### (2)类型限制和默认值设置
> 使用前必须引入 `prop-types.js`文件
>
> ```jsx
> class MyComponent extends React.Component{
> state = {
> weather: 'isHot'
> }
> render(){
> const { name, age, grade } = this.props;
> return (
>
> - {name}
> - {age}
> - {grade}
>
> )
> }
> }
> //对传递进MyComponent的参数进行限制
> MyComponent.propTypes = {
> name : PropTypes.string.isRequired,
> age : PropTypes.number,
> grade : PropTypes.number,
> }
> //对传递进MyComponent的参数设置默认值
> MyComponent.defaultProps = {
> name : '陶贼帅'
> }
>
> const obj = {
> name: '帅哥',
> age: 22,
> grade: 80
> }
>
> let app = (
>
>
>
> //等效于上面的展开一项一项传递
>
> )
> ```
>
> ```jsx
> 类对象名.propTypes = {
> 属性名: PropTypes.类型名.isRequired,
> 属性名: PropTypes.类型名,
> }
> //类型名都为小写,有string,number,object
> //注意如果要限制的类型为函数则使用func
> //isRequired定义该变量是否必须
>
> 类对象名.defaultProps = {
> 属性名: 默认值,
> }
> ```
### (3)prop简写形式
> ```jsx
> class MyComponent extends React.Component{
> static propTypes = {
> name : PropTypes.string.isRequired,
> age : PropTypes.number,
> grade : PropTypes.number,
> }
>
> static defaultProps = {
> name : '陶贼帅'
> }
>
> render(){
> const { name, age, grade } = this.props;
> return (
>
> - {name}
> - {age}
> - {grade}
>
> )
> }
> }
> ```
>
> 将组件相关的都放入类中,让其为静态变量,所有的实例对象只有一份。
### (4)注意事项
* props为只读属性
* 构造函数经过简写方法后完全可以不写,但是写的话必须接收props和调用super(props)传递参数
> ```jsx
> constructor(props){
> super(props)
> //如果不调用该方法,使用后会出现this.props为undefined的bug。
> //构造器是否接收props取决于是否有传递参数
> }
> ```
## 10.refs
### (1)简单用法(字符串形式)(已经过时)
使用`this.refs.自定义名`可以直接拿到该对象的真实DOM节点
> ```jsx
> class MyComponent extends React.Component{
>
> clickBtn = () => {
> const { input1 } = this.refs
> alert( input1.value );
> }
>
> inputBlur = () => {
> const { input2 } = this.refs
> alert( input2.value );
> }
>
> render(){
> return (
>
>
>
>
>
> )
> }
> }
>
> let app = (
>
>
>
> )
>
> ReactDOM.render(app, document.getElementById('app'))
> ```
### (2)回调方法使用(内联函数方式)
**此方式写的最多**
> ```jsx
> class MyComponent extends React.Component{
>
> clickBtn = () => {
> const { input1 } = this
> alert( input1.value );
> }
>
> inputBlur = () => {
> const { input2 } = this
> alert( input2.value );
> }
>
> render(){
> return (
>
> this.input1 = currentNode } type="text" placeholder="输入内容"/>
>
> this.input2 = currentNode } onBlur={ this.inputBlur } type="text" placeholder='失去焦点显示输入内容'/>
>
> )
> }
> }
>
> ```
>
> 使用回调函数的方式,react会将参数赋值为当前的DOM元素,通常的使用方法是在当前对象上挂在一个变量存储他。但是使用[^内联函数]时会出现在页面状态更新时,[^内联函数]会调用两次的情况。大多数情况这个都是无关紧要的。
>
> [^内联函数]: 直接在ref中写一个函数体就是内联函数。
### (3)回调方法使用(内部绑定方式)
**正常情况下和上面没什么区别,且写法麻烦**
> ```jsx
> class MyComponent extends React.Component{
>
> clickBtn = () => {
> const { input1 } = this
> alert( input1.value );
> }
>
> inputBlur = () => {
> const { input2 } = this
> alert( input2.value );
> }
>
> input2Ref = ( currentNode ) => {
> this.input2 = currentNode
> }
>
> render(){
> return (
>
> this.input1 = currentNode } type="text" placeholder="输入内容"/>
>
>
>
> )
> }
> }
> ```
### (4)使用createRef
**createRef是专人专用的,其中只会存一个dom元素,通过`this.自定义名字.current`来调用,使用方式最麻烦,但是react最推荐。**
> ```jsx
> class MyComponent extends React.Component{
>
> myInput1Ref = React.createRef();
> myInput2Ref = React.createRef();
>
> clickBtn = () => {
> const input1 = this.myInput1Ref.current;
> alert( input1.value );
> }
>
> inputBlur = () => {
> const input2 = this.myInput2Ref.current;
> alert( input2.value );
> }
>
> input2Ref = ( currentNode ) => {
> this.input2 = currentNode
> }
>
> render(){
> return (
>
>
>
>
>
> )
> }
> }
> ```
## 11.事件绑定
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件--------为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
(2).通过event.target得到发生事件的DOM元素对象
**当绑定事件的时候,在绑定的事件身上有个event参数,可以使用event.target来实现ref的相关功能**
**注意:所有的绑定的事件都有event属性**
**传参方式1:**
```jsx
class MyComponent extends React.Component{
state = {
username: '',
password: ''
}
inputChange = (type, value) => {
this.setState({ [type]: value });
}
render(){
return (
{ this.inputChange('username', event.target.value) } } type="text" placeholder="用户名"/>
{ this.inputChange('password', event.target.value)} } onBlur={ this.inputBlur } type="text" placeholder='密码'/>
)
}
}
```
方式二:到13.高阶函数
## 12.受控组件
页面中的所有输入类的组件都是受控组件,都必须要绑定`onChange`事件。
## 13.高阶函数(用于事件绑定传参)
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。I
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
> 事件绑定是假如不做处理直接调用函数并传递参数,会出现问题,需要在函数体内部再返回一个函数作为回调函数给react使用。
>
> ```jsx
> class MyComponent extends React.Component{
>
> inputChange(type){
> return ( event ) => {
> console.log( type, event.target.value );
> }
> }
>
> render(){
> return (
>
>
>
>
>
> )
> }
> }
> ```
>
> **返回的函数才是react使用的回调函数,react会配置event参数**
## 14.生命周期钩子
### (1)使用方式
```jsx
class MyComponent extends React.Component{
state = {
username: '',
password: '',
opacity: 1
}
inputChange = (type, value) => {
this.setState({ [type]: value });
}
unMount = () => {
//卸载掉某个节点内的组件
ReactDOM.unmountComponentAtNode(document.getElementById("app"))
}
//挂载结束后执行一次
componentDidMount(){
console.log(1)
this.timer = setInterval( ()=> {
let opacity = this.state.opacity - 0.1;
if ( opacity <= 0) opacity = 1;
this.setState({opacity})
}, 200 );
}
//组件将要卸载时执行
componentWillUnmount(){
clearInterval(this.timer);
}
//页面初始化和页面数据更新时调用
render(){
return (
{ this.inputChange('username', event.target.value) } } type="text" placeholder="用户名"/>
{ this.inputChange('password', event.target.value)} } onBlur={ this.inputBlur } type="text" placeholder='密码'/>
波哥
)
}
}
```
### (2)生命周期(旧)
.png)
`componentWillMount`: 组件挂载前触发,一般不用。(17.0后废弃)
`componentDidMount`:组件完成挂载时触发 -----------常用,一般用于初始化事件:如设置定时器,发送请求,订阅消息。
`componentWillReceiveProps`:组件接收父组件传递来的参数时触发(第一次传递时不会触发该函数,第一个参数可以接受到props)。(17.0后废弃)
`shouldComponentUpdate`: 决定组件是否可以更新,返回值会true可更新,false不可更新。(强制更新是不会触发,会直接跳过)
`componentWillUpdate`: 组件将要更新时触发,一般不用。(17.0后废弃)
`componentDidUpdate`:组件更新完成后触发。
`componentWillUnmount`:组件将要卸载时触发。----------------常用,用于收尾工作。(`ReactDOM.unmountComponentAtNode()`用于卸载组件)
**执行顺序非常重要**
### (3)生命周期(新)
废弃了三个生命周期钩子,新增了两个生命周期钩子。**其他使用方法一致**
* 废弃的:
* `componentWillMount`
* `componentWillReceiveProps`
* `componentWillUpdate`
* 新增的
* `getDerivedStateFromProps`: 用处很罕见,基本不会用到,在组件中的state在任何时候完全取决于props时用。他return的可以是一个对象或者null,如果返回null时不影响页面功能,但是返回对象时,会覆盖掉state,并且无法做修改。
```js
static getDerivedStateFromProps(props, state){
return props
}
```
* `getSnapshotBeforeUpdate`: 用处很罕见,基本不会用到,在更新前获从`dom`中获得一些信息(例如滚动位置),此生命周期的任何返回值将作为参数的形式传给`componentDidUpdate`。
```jsx
getSnapshotBeforeUpdate(prevProps, prevState){
return "XXX";
}
componentDidUpdate( prevProps, prevState, snapshotValue ){
//snapshotValue接收的就是“XXX"
}
```
使用方法:(当添加元素时,浏览的当前内容始终在屏幕中不动)
```jsx
class MyComponent extends React.Component{
state = {
listArr: ['消息2', '消息1']
}
componentDidMount(){
let count = this.state.listArr.length + 1;
setInterval( ()=> {
count += 1;
let listArr = this.state.listArr
listArr.unshift('消息'+ count);
this.setState({ listArr })
}, 1000 )
}
getSnapshotBeforeUpdate( prevProps, prevState ){
return this.refs.listBox.scrollHeight
}
componentDidUpdate ( prevProps, prevState, height ){
console.log( height );
this.refs.listBox.scrollTop += this.refs.listBox.scrollHeight - height
}
render(){
return (
{
this.state.listArr.map( ( item, index ) => {
return
{item}
} )
}
)
}
}
ReactDOM.render( , document.getElementById('app'))
```
## 经典面试题:key
**1). react/vue中的key有什么作用?(key的内部原理是什么?)**
**2).为什么遍历列表时,key最好不要用index?**
**3).key应该用什么值**
**答:1. 虚拟DOM中key的作用:**
1)简单的说: key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变,直接使用之前的真实DOM。
(2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
b.旧虚拟DOM中未找到与新虚拟DOM相同的key根据数据创建新的真实DOM,随后渲染到到页面。
**2.用index作为key可能会引发的问题:**
1)若对数据进行:逆序添加、逆序除等破坏顺序操作:会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
2)如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
3)注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
**3.开发中应该怎么选key**
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
# 二、react应用(基于脚手架)
## 1.创建项目并运行
(1)全局安装:npm install -g create-react-app
(2)创建项目: create-react-app 项目名
(3)cd到项目中,运行npm start
## 2.项目结构目录
### (1)**public ---- 静态资源文件夹**
```text
favicon.icon ------ 网站页签图标
index.html--------主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
```
### (2)**src ---- 源码文件夹**
```text
App.css -------- App组件的样式
App.js--------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 通用样式
index.js ------ 入口文件
logo.svg ------- logo图
reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)
```
## 开发中项目文件夹排版和命名规则
* 组件统一放在component文件夹下,一个组件一个文件夹。
* 组件文件和文件夹名首字母都大写。
* 组件文件可以用`.jsx`后缀的文件来区分普通js文件。
* 组件也可以用`index.jsx`来命名,这样引入的时候导入到外层文件夹就可以不用写到index。
* 组件相关的文件统一放在组件文件夹下,做到组件统一管理组件内所有的资源。
## 快速生成代码片段
在`js`或者`jsx`中输入`rcc`回车生成class组件形式,输入`rfc`生成function组件
## 3.配置代理解决跨域
### (1)方法一(简单方法,不常用)
在package.json中的末尾添加代理。
> ```jsx
> "proxy": "http://localhost:5000"
> ```
在axios中,使用运行地址。
> ```jsx
> axios.get("http://localhost:3000/students").then( res=> {
> console.log(res)
> }, err => {
> console.log(err)
> } )
> ```
**注意:该方法是在本地设置一个代理服务器,先请求代理服务器,让代理服务器去发送请求,如果请求文件或内容时本地有,则优先获取本地,否则才会去远程服务器上获取。且只能配置一个代理。**
说明:
1. 优点:配置简单,前端请求资源时可以不加任何前缀。
2. 缺点:不能配置多个代理。
3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
### (2)方法二
1. 第一步:创建代理配置文件
```
在src下创建配置文件:src/setupProxy.js
```
2. 编写setupProxy.js配置具体代理规则:
```js
const proxy = require('http-proxy-middleware')//react脚手架自带的,不需要下载
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': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
```
说明:
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
## 4.组件间通信
* 父子组件通信
* 父组件传给子组件
> ```jsx
> //父组件
> export default class Parent extends Component {
> render() {
>
> return (
>
>
>
> )
> }
> }
>
> //子组件
> export default class Child extends Component {
> render() {
>
> return (
>
> { this.props.自定义名 }
>
> )
> }
> }
> ```
* 子组件传递给父组件
> ```jsx
> //父组件
> export default class Parent extends Component {
>
> setData = ( data ) => {
> console.log( data )
> }
>
> render() {
>
> return (
>
>
>
> )
> }
> }
>
> //子组件
> export default class Child extends Component {
>
> render() {
> this.props.setData( 'hello react' )
> return (
>
> { this.props.自定义名 }
>
> )
> }
> }
> ```
* 兄弟组件之间通信(使用发布订阅模式)
这里使用的是第三方库-----PubSubJS来使用封装好的发布订阅模式。使用方法:
> ```jsx
> //1.安装包:yarn add pubsub-js
> //2.在所有需要使用发布订阅模式的包中导入 import PubSub from "pubsub-js"
> //3.PubSub.publish( msg , data ) 发布
> /*4.this.token = PubSub.subscribe( msg, ( msg, data ) => {
> console.log( data );
> }) 订阅*/
> /*5. PubSub.unsubscribe( this.token ); 取消订阅*/
>
>
> //发布
> export default class Subscribe extends Component {
> publish = () => {
> PubSub.publish( "My Topic", "hello react" )
> }
> render() {
>
>
> return (
>
> Subscribe
>
>
> )
> }
> }
>
> //订阅
> export default class Public extends Component {
>
> componentDidMount(){
> this.token = PubSub.subscribe( "My Topic", ( msg, data ) => {
> console.log( data );
> })
> }
>
> componentWillUnmount(){
> PubSub.unsubscribe( this.token );
> }
>
> render() {
>
> return (
>
> Public
>
> )
> }
> }
> ```
>
## 5.路由
**原理:是通过路由器中的history对象往路由的栈中压入新的记录,通过路由检测器检测到变化,来更新对应的组件,在不刷新页面的情况下来改变页面。hash模式是使用的锚点进行链接,适配性强**
### **(1)react-router路由的理解**
react-router有三种,分别是:
* react-router-dom专门用于web页面
* 用于react-native的
* 用于anywhere的。
### **(2)使用react-router-dom**
* 安装库: yarn add react-router-dom
* `BrowserRouter`代码实现部分
导航区写link标签,展示区route标签
> ```jsx
> //在index.js中引入BrowserRouter,注意在实用link和Routes时,BrowserRouter可以直接在最外层写好,且routes和link必须在一个BrowserRouter才能正常切换组件,这样放到最外层一劳永逸。
> import React from 'react';
> import ReactDOM from 'react-dom';
> import App from './App';
> import { BrowserRouter } from "react-router-dom";
>
> ReactDOM.render(
> ,
> document.getElementById('root')
> );
>
>
> //首先引入Link, Route, Routes
> import React, { Component } from "react";
> import { Link, Route, Routes } from "react-router-dom";
> import About from "./About";
> import Home from "./Home";
>
> export default class App extends Component {
> render() {
> return (
>
>
>
>
>
React Router Demo
>
>
>
>
>
>
>
> About
>
>
> Home
>
>
>
>
>
>
> {/*注册路由,从6以上版本需要使用Routes包裹,5以下直接写route*/}
> {/*6.0以上版本使用方法*/}
>
> }>
> }>
> }>
>
> {/*6.0以下版本使用方法*/}
>
>
>
>
>
>
> );
> }
> }
>
> ```
* 如果要让其可以自定义选中样式,将link改为`NavLink`使用
```jsx
About
Home
```
* 封装NavLink
```jsx
内容体部分
about
home
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
return (
)
}
}
```
**标签的内容体部分会通过children属性传递进去,组件中通过`this.props.children`得到。** 在`NavLink`中也是通过children来传递进入的,所以可以直接将props直接传递进去。
### (3)路由多次匹配问题
**注意:问题:在6.0以下版本使用route注册的路由会在路径改变的时候进行多次匹配,多次匹配的结果会造成效率的问题**
**6.0以上版本用Routes包裹住之后就不会出现该问题,只要匹配到就会停止匹配**
**解决方案:**
> ```jsx
> import { Route, Switch } from "react-router-dom";
>
>
>
> }>
> }>
> }>
>
> ```
### (4)多层路由刷新后样式丢失
代码片段:
> ```jsx
> export default class App extends Component {
> render() {
> return (
>
>
>
>
>
React Router Demo
>
>
>
>
>
> );
> }
> }
> ```
产生原因:在react中当出现不存在路由的时候,会直接将index.html返回回去。在使用多层路由时,刷新前是正常显示的,css路径为`http://localhost:3000/css/bootstrap.css`,但是在刷新后出现问题变成了`http://localhost:3000/shuai/css/bootstrap.css`,所以导致了问题的出现。
**解决方法:**
(1)去掉index.html中引入css样式表使用路径中的`.`,`./`的意思是在当前路径下触发
> ```jsx
>
> //变为
>
> ```
(2)将路径写为public的绝对路径`%PUBLIC_URL%`
> ```jsx
>
> ```
(3)将`BrowserRouter`该为`HashRouter`, 此方法不常用
### (5)路由的模糊匹配和严格比配
在6.0以下版本中,路由会出现模糊匹配,例子:
> ```jsx
>
> Home
>
>
> ```
>
> 这样子也会匹配成功。就是模糊匹配
>
> 开启严格模式:
>
> ```jsx
>
> Home
>
>
> ```
>
> 这样匹配会失败
>
> **严格模式不能随便开,可能会导致无法匹配二级路由**
在6.0版本默认开启严格模式,要开启接收子路由:
> ```jsx
> }>
> ```
### (6)重定向
**重定向一般写在最后**
> ```jsx
> //5.0使用的是Redirect
>
>
>
>
>
>
>
> //6.0使用的是Navigation
>
> }>
> }>
>
> ```
### (7)路由嵌套
**6.0版本**
> ```jsx
> //父组件中
>
> }>
> }>
> }>
>
>
> //Home组件中
>
> -
> news
>
> -
> message
>
>
> {/*此处路径前不加/*/}
>
> }>
> }>
> }>
>
> ```
**5.0版本**
> ```jsx
> //父组件中
>
>
>
>
>
>
> //Home组件中
>
> -
> news
>
> -
> message
>
>
> {/*此处路径前不加/, 如果开启严格模式,则无法匹配子路由 */}
>
>
>
>
>
> ```
### (8)路由传参
#### ①传递params参数
**6.0** **注意:此方法只能用在函数式组件中**
> ```jsx
> //在申明该组件的路由时
> }>
>
> //message组件
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
>
>
> }>
>
>
> );
> }
>
> //Detail组件-----子组件需要使用函数式组件才可以使用useParams()
> import React from "react";
> import { useParams } from "react-router-dom";
>
> export default function Detail() {
> const { id } = useParams()
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> let { title, content } = MessageData.find( ( obj ) => {
> return obj.id === parseInt(id)
> })
> return (
>
> id: {id}
> title: {title}
>
> content:{content}
>
>
> );
> }
> ```
**5.0**
> ```jsx
> //父组件
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
>
>
>
>
>
> );
> }
>
>
> //子组件
>
> import React, { Component } from 'react'
>
> export default class index extends Component {
> render() {
> const { id } = this.props.match.params
> return (
>
>
>
> )
> }
> }
>
> ```
#### ②通过search传参
**6.0**
> ```jsx
> //message组件中
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
>
>
> }>
>
>
> );
> }
>
>
> //detail组件中
> import React from "react";
> import { useLocation, useParams } from "react-router-dom";
> import qs from 'querystring'
>
> export default function Detail() {
> // 接收search传递的参数
> const { id } = qs.parse(useLocation().search.slice(1));
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> let { title, content } = MessageData.find( ( obj ) => {
> return obj.id === parseInt(id)
> })
>
> return (
>
> id: {id}
> title: {title}
>
> content:{content}
>
>
> );
> }
>
> ```
**5.0**
> ```jsx
> //父组件
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
> {/*路由正常注册*/}
>
>
>
>
> );
> }
>
>
> //子组件
>
> import React, { Component } from 'react'
> import qs from 'querystring'
>
> export default class index extends Component {
> render() {
>
> const { id } = qs.parse(this.props.location.search.splice(1))
> return (
>
>
>
> )
> }
> }
> ```
#### ③传递state参数
**注意:是路由组件中独有的状态,不是组件中的状态state,该方法传递的参数在地址栏不可见。刷新后不丢失。**
**6.0**
> ```jsx
> //message组件
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
>
>
> }>
>
>
> );
> }
>
>
>
> //detail组件中
> import React from "react";
> import { useLocation , useParams } from "react-router-dom";
> import qs from 'querystring'
>
> export default function Detail() {
> // 接收state传递的参数
> const { state: obj } = useLocation();
> let { id, title, content } = obj
>
> return (
>
> id: {id}
> title: {title}
>
> content:{content}
>
>
> );
> }
>
> ```
**5.0**
> ```jsx
> //父组件
> render() {
> const MessageData = [
> { id: 1, title: "1233", content: "我真帅" },
> { id: 2, title: "4566", content: "中国真好" },
> { id: 3, title: "7899", content: "我还是很帅" },
> ];
> return (
>
>
> {MessageData.map((item, index) => {
> return (
> -
> message001
>
> );
> })}
>
> {/*路由正常注册*/}
>
>
>
>
> );
> }
>
>
> //子组件
>
> import React, { Component } from 'react'
>
> export default class index extends Component {
> render() {
> const { id } = this.props.location.state;
> return (
>
>
>
> )
> }
> }
> ```
### (9)push和replace
默认情况下,路由跳转是通过push的方式进行压栈操作的,有些情况下需要进行replace操作。
**6.0和5.0在此处使用方法一致**
> ```jsx
> message001
> ```
### (10)编程式路由
#### **`v5`版本**
* push跳转+携带`params`参数
> ```js
> this.props.history.push(`/b/child1/${id}/${title}`);
> ```
* push跳转+携带search参数
> ```js
> this.props.history.push(`/b/child1?id=${id}&title=${title}`);
> ```
* push跳转+携带state参数
> ```js
> this.props.history.push(`/b/child1`, { id, title });
> ```
* replace跳转+携带`params`参数
> ```js
> this.props.history.replace(`/home/message/detail/${id}/${title}`)
> ```
* replace跳转+携带search参数
> ```js
> this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
> ```
* replace跳转+携带state参数
> ```js
> this.props.history.replace(`/home/message/detail`, { id, title });
> ```
* 前进
> ```js
> this.props.history.goForward();
> ```
* 后退
> ```js
> this.props.history.goForward();
> ```
* 跳转到
> ```js
> this.props.history.go(-2); //回退到前2条的路由
> ```
* 在一般组件中如果要是用`this.props.history`,需要使用hook函数,一般组件就是指不是使用route显示出来的组件。
> ```js
> import {withRouter} from 'react-router-dom'
>
> class Header extends Component {
> // withRouter(Header)后,就可以在一般组件内部使用 this.props.history
> //...
> }
>
> export default withRouter(Header)
>
> ```
#### **`v6`版本**
```js
//先行代码,必须使用函数式组件使用hook函数来实现
// v6版本编程导航使用 useNavigate (以下为引入代码)
import { useNavigate } from "react-router-dom";
export default function A() {
const navigate = useNavigate();
//...
}
```
* push跳转+携带`params`参数
> ```js
> navigate(`/b/child1/${id}/${title}`);
> ```
* push跳转+携带search参数
> ```js
> navigate(`/b/child2?id=${id}&title=${title}`);
> ```
* push跳转+携带state参数
> ```jsx
> navigate("/b/child2", { state: { id, title }});
> ```
* replace跳转+携带`params`参数
> ```jsx
> navigate(`/b/child1/${id}/${title}`,{replace: true});
> ```
* replace跳转+携带search参数
> ```js
> navigate(`/b/child2?id=${id}&title=${title}`,{replace: true});
> ```
* replace跳转+携带state参数
> ```js
> navigate("/b/child2", { state: { id, title },replace: true});
> ```
### (11)`HashRouter`和`BrowserRouter`的区别
1.底层原理不一样:
`BrowserRouter`使用的是`H5`的`history API`,不兼容`IE9`及以下版本。`HashRouter`使用的是URL的哈希值。
2.`url`表现形式不一样
`BrowserRouter`的路径中没有#,例如:` loca1host:3000/demo/test`;`HashRouter`的路径包含#,例如: `localhost: 3000/#/demo/test`
3.刷新后对路由state参数的影响
(1).`BrowserRouter`没有任何影响,因为state保存在history对象中。
(2).`HashRouter`刷新后会导致路由state参数的丢失。!!!
4.备注: `HashRouter`可以用于解决一些路径错误相关的问题。
## 6.`AntDesign`
### (1)按需引入,3.x
1.导入包
> ```js
> yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
> ```
2.修改`package.json`
> ```js
> "scripts": {
> "start": "react-app-rewired start",
> "build": "react-app-rewired build",
> "test": "react-app-rewired test",
> "eject": "react-scripts eject"
> },
> ```
3.在根目录新建一个`config-overrides.js`
> ```js
> const { override, fixBabelImports } = require('customize-cra');
>
> module.exports = override(
> fixBabelImports('import', {
> libraryName: 'antd',
> libraryDirectory: 'es',
> style: 'css',
> }),
> );
> ```
### (2)修改主题颜色,4.x
1.导入包
> ```js
> yarn add craco-less
> ```
2.修改`package.json`
> ```js
> "scripts": {
> "start": "craco start",
> "build": "craco build",
> "test": "craco test",
> "eject": "craco eject"
> },
> ```
3.在根目录新建一个`craco.config.js`
> ```js
> const CracoLessPlugin = require('craco-less');
>
> module.exports = {
> plugins: [
> {
> plugin: CracoLessPlugin,
> options: {
> lessLoaderOptions: {
> lessOptions: {
> modifyVars: { '@primary-color': '#1DA57A' },
> javascriptEnabled: true,
> },
> },
> },
> },
> ],
> };
> ```
## 7.Redux
原理图
### 7.1. redux的三个核心概念
#### (1). action
1. 动作的对象
2. 包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性
3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
#### (2). reducer
1. 用于初始化状态、加工状态。
2. 加工时,根据旧的state和action, 产生新的state的**纯函数****
#### (3). store
1. 将state、action、reducer联系在一起的对象
2. 如何得到此对象?
1)import {createStore} from 'redux'
2)import reducer from './reducers'
3)const store = createStore(reducer)
3. 此对象的功能?
1) getState(): 得到state
2) dispatch(action): 分发action, 触发reducer调用, 产生新的state
3) subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
### 7.2 `redux`基本使用方法(精简版)
* yarn add redux添加redux依赖
* 在根目录中创建redux包创建`store.js`
(1)使用redux的createStore创建store对象并暴露
> ```jsx
> /*
> 该文件用于暴露一个store对象,整个应用只有一个store对象
> */
> // 引入createStore,创建核心的store对象
> import { createStore } from 'redux'
> // 引入为count组件服务的reducer
> import countReducer from './countReducer'
> // 暴露store
> export default createStore( countReducer )
>
> ```
(2)创建为count组件服务的reducer
> ```jsx
> /*
> 1.该文件用于创建一个专门为count服务的reducer文件,reducer的本质是一个函数
> 2.reducer会接收到两个参数,分别为:preState(之前的状态), 动作对象(action)
> */
>
> const initState = 0;
> export default function countReducer( preState = initState, action ){
> // 从action中获取:type和data对象
> const { data, type } = action;
> // 根据type决定如何加工
> switch (type) {
> case "increament":
> return data * 1 + preState;
>
> case "decreament":
> return preState - data * 1;
>
> default:
> return preState;
> }
> }
> ```
(3) 在count组件中使用
> ```jsx
> import React, { Component } from 'react'
> import store from '../../redux/store'
>
> export default class Count extends Component {
> state = {
> numInput: ''
> }
>
> // 因为store在改变状态的时候只关注状态,并不会引起页面重新渲染。所以可以使用this.setState进行重新渲染,但是这样写的话每个组件中都必须写。
> // componentDidMount(){
> // // store.subscribe会在store状态更新时触发
> // store.subscribe( () => {
> // this.setState({});
> // } )
> // }
>
> // 处理输入的函数
> numInput = ( event ) => {
> this.setState({ numInput: event.target.value });
> }
>
> // 处理加法的函数
> increatment = () => {
> let numInput = this.state.numInput
> store.dispatch({ type: 'increament', data: numInput * 1 })
> }
>
>
> // 处理减法的函数
> decreatment = () => {
> let numInput = this.state.numInput
> store.dispatch({ type: 'decreament', data: numInput * 1 })
> }
>
> increatmentIfOdd = () => {
> let numInput = this.state.numInput
> console.log( store.getState()*1 / 2 === 0 )
> if(store.getState()*1 / 2 === 0) {
> return;
> }
> store.dispatch({ type: 'increament', data: numInput * 1 })
> }
>
>
> increatmentAsync = () => {
> let numInput = this.state.numInput
> setTimeout(() => {
> store.dispatch({ type: 'increament', data: numInput * 1 })
> }, 500);
> }
>
> render() {
> return (
>
>
当前值为{ store.getState() }
>
>
>
>
>
>
> )
> }
> }
>
> ```
(4)一劳永逸的另组件重新渲染方法,直接在index.js下添加
> ```jsx
> store.subscribe( () => {
> ReactDOM.render(
>
>
> ,
> document.getElementById('root')
> );
> })
>
> ```
>
### 7.3 `redux`完整版
在精简版的基础上添加两个文件
(1)`countAction.js`
> ```jsx
> import { DECREATEMENT, INCREATEMENT } from './constant'
>
> // 专门用于创建action对象
> export const createIncreatementAction = data => ( { type: INCREATEMENT, data } )
> export const createDecreatementAction = data => ( { type: DECREATEMENT, data } )
> ```
(2)`constant.js`
> ```jsx
> // 用于使用定量
> export const INCREATEMENT = "increatement"
> export const DECREATEMENT = "decreatement"
> ```
### 7.4异步action
action为普通对象的时候则是同步,如果是函数则可以使用异步。
1.导入`redux-thunk`包
2.创建action的对象不再返回一个函数,而是返回一个函数,在函数中使用异步方法。
> ```jsx
> // 创建异步action
> export const createIncreatmentAsyncAction = ( data, time ) => {
> return ( dispatch ) => {
> setTimeout(() => {
> dispatch( createIncreatementAction( data ) )
> //也可以使用store.dispatch( createIncreatementAction( data ) ),因为是store调用该函数,会在函数上添加一个dispatch参数。
> }, time);
> }
> }
> ```
3.使用异步action(和同步action使用方法一致)
> ```jsx
> store.dispatch( createIncreatmentAsyncAction( numInput, 500 ) )
> ```
### 7.5 `react-redux`
`react-redux`是通过`容器组件`和`redux`进行通信,`ui组件`只用于页面展示和容器组件交互数据。

(1)添加react-redux依赖:yarn add react-redux
(2)创建容器组件
> ```jsx
>
> import CountUI from '../../component/Count'
> import { createIncreatementAction, createDecreatementAction, createIncreatmentAsyncAction } from '../../redux/countAction'
>
> // 引入connect连接UI组件和redux
> import { connect } from 'react-redux'
>
>
> function mapStateToProps( state ){
> return { count: state }
> }
>
> function mapDispatchToProps( dispatch ){
> return {
> jia: value => { dispatch( createIncreatementAction( value ) ) },
> jian: value => { dispatch( createDecreatementAction( value ) ) },
> jiaAsync: ( value, time ) => { dispatch( createIncreatmentAsyncAction( value, time ) ) }
> }
> }
>
> //连接ui组件并将mapStateToProps和mapDispatchToProps传递给props对象, UI组件通过再次调用传入
> export default connect( mapStateToProps, mapDispatchToProps )( CountUI )
> ```
>
> ```jsx
> //优化版
>
> import CountUI from '../../component/Count'
> import { createIncreatementAction, createDecreatementAction, createIncreatmentAsyncAction } from '../../redux/countAction'
>
> // 引入connect连接UI组件和redux
> import { connect } from 'react-redux'
>
> export default connect(
> state => ({ count: state }),
> // 一般写法
> /*dispatch => (
> {
> jia: value => { dispatch( createIncreatementAction( value ) ) },
> jian: value => { dispatch( createDecreatementAction( value ) ) },
> jiaAsync: ( value, time ) => { dispatch( createIncreatmentAsyncAction( value, time ) ) }
> }
> )*/
>
> /* 简化版写法
> mapDispatchToProps也可以是一个对象,对象的value值直接是一个action方法,action方法一定定义好且已经可以接受参数
> */
> {
> jia: createIncreatementAction,
> jian: createDecreatementAction,
> jiaAsync: createIncreatmentAsyncAction
> }
>
> )( CountUI )
> ```
(3)使用
> ```jsx
> //在父组件中
> import React, { Component } from 'react'
> import Count from './container/Count'
> import store from './redux/store'
>
> export default class App extends Component {
> render() {
> return (
>
> {/* 使用容器组件并传递store */}
>
>
> )
> }
> }
>
> ```
>
> ```jsx
> //在ui组件中
> this.props.***就可以访问到容器组件定义的状态或者方法
> ```
>
> 优化后
>
> ```jsx
> //在父组件中
> import React, { Component } from 'react'
> import Count from './container/Count'
>
> export default class App extends Component {
> render() {
> return (
>
>
>
> )
> }
> }
>
> ```
>
> ```jsx
> //index.js中
> import React from 'react';
> import ReactDOM from 'react-dom';
> import App from './App';
> import store from './redux/store'
> import { Provider } from 'react-redux'
>
> //provider可以给所有的容器组件传递一个store值,不需要再次给
> ReactDOM.render(
>
> {/* 用provider进行包裹,可以让所有的容器组件都能接受到store */}
>
>
>
> ,
> document.getElementById('root')
> );
> ```
>
>
### 7.6 多组件使用`redux`
store文件中要做修改:
```jsx
/*
该文件用于暴露一个store对象,整个应用只有一个store对象
*/
// 引入createStore,创建核心的store对象,applyMiddleware是添加中间件,combineReducers可以将多个reducer集中处理
import { createStore, applyMiddleware, combineReducers } from 'redux'
// 引入为count组件服务的reducer
import countReducer from './reducers/count'
// 引入thunk使用函数式action
import thunk from 'redux-thunk'
import personReducer from './reducers/person'
const allReducers = combineReducers({
count: countReducer,
persons: personReducer
})
// 暴露store
export default createStore( allReducers, applyMiddleware( thunk ) )
```
在容器组件中使用
```jsx
import { connect } from 'react-redux'
connect(
state => ({count: state.count,***}),
{
****
}
)(ui组件)
```
### 7.7纯函数
1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
2. 必须遵守以下一些约束
1) 不得改写参数数据
2) 不会产生任何副作用,例如网络请求,输入和输出设备
3) 不能调用Date.now()或者Math.random()等不纯的方法
3. redux的reducer函数必须是一个纯函数
### 7.8redux开发者工具
(1)安装依赖:yarn add redux-devtools-extension
(2)chrom商店安装工具
(3)在store中配置
```jsx
import { composeWithDevTools } from 'redux-devtools-extension'
export default createStore( allReducers, composeWithDevTools( applyMiddleware( thunk ) ) )
```
## 8.项目打包
(1)yarn build
(2)生成build必须在服务器上运行
(3)可以使用serve模拟服务器环境:
1. npm i serve -g
1. serve build //将build作为服务器的根路径
## 9.扩展
### (1)`setState()`
`setState`是异步调用的,他属于eventloop。
`setState`有两种用法:
1.对象式:`setState({}, [callback] )`
callback函数会在`setState`更改状态完成后进行调用。
2.函数式:`setState( updater, [callback] )`
* updater:是返回`stateChange`对象的函数
* updater可以接受到state和props
* callback是可选回调函数
```jsx
setState( ( state, props ) => {
return {}
}, [callback] )
```
**使用原则**
* 如果新状态不依赖于原状态 ==》 使用对象方式
* 如果新状态依赖于原状态 ==》 使用函数方式
* 如果需要在状态更新后获取更新后数据,使用callback
### (2)lazyload
**此方法用于路由组件上**
```jsx
import React, { Component, lazy, Suspense } from "react";
// 路由组件的懒加载
const About = lazy( () => import('./About') )
const Home = lazy( () => import('./Home') )
//在routes组件外套一层suspense来,在组件未加载出来的时候优先显示fallback中的组件
loading }>
}>
}>
}>
```
### (3)Hooks
1. React Hook/Hooks是什么?
```
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
```
2. 三个常用的Hook
```
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
```
3. State Hook
```
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
```
4. Effect Hook
```
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作(在componentDidMount的操作)
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 检测数组,会检测数组中的数据,只有当数组中的数据改变时才会再次触发,不写则不做检测,谁变了都会调用
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
```
5. Ref Hook
```
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
```
6. `useCallback` 记忆函数
```js
//当函数式组件中状态改变的时候会重新调用该函数,使用该钩子可以防止方法被重新创建,起到缓冲作用
//用法只是在函数的外层再套一层useCallback
function myComponent(){
const [ name, setName ] = useState( "shuaibo" )
let click = useCallback( ( arg )=>{
setName( name => name="帅波" )
...
}, [ name ] )//只有当检测中包含的值改变的时候才会重新创建
//问题:假如这个函数不被更新,那么其中用到state变量就一直是初始化状态的值,所以用到state中的值的时候必须在检测数组中添加
}
```
7. `useMemo` 记忆组件
```js
//可以平替useCallback, 区别是useCallback不会执行第一个函数,而useMemo会执行第一个函数,将结果返回
function myComponent(){
const [ name, setName ] = useState( "shuaibo" )
let click = useCallback( () =>
( arg ) => {
setName( name => name="帅波" )
...
}, [ name ] )//只有当检测中包含的值改变的时候才会重新创建
//问题:假如这个函数不被更新,那么其中用到state变量就一直是初始化状态的值,所以用到state中的值的时候必须在检测数组中添加
}
//有些类似于vue中的计算属性
```
8. `useContext` (减少跨级传值组件的层级)
```jsx
//使用跨级传参使用的context,用法和类组件中基本一致,可以完全照搬不会出问题
const shuaiContext = React.createContext()
//父组件中使用还是
function Parent(){
const { Provider } = shuaiContext
const [ name, setName ] = useState("shuai")
return (
)
}
//主要区别是在子组件中
function Children(){
const value = useContext( shuaiContext )
return(
{ value }
)
}
```
### (4) Fragment
#### 使用
<>>
#### 作用
> 可以不用必须有一个真实的DOM根标签了
### (5)context
#### 理解
> 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
#### 使用
```js
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
子组件
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
{
value => ( // value就是context中的value数据
要显示的内容
)
}
```
#### 注意
在应用开发中一般不用context, 一般都用它的封装react插件
#### 例子
> ```jsx
> import React, { Component } from "react";
>
> const nameContext = React.createContext();
> const { Provider } = nameContext
> class A extends Component {
> state = {
> name : '陶贼帅'
> }
> render() {
> return (
>
>
this is A component 传递数据:{this.state.name}
>
>
>
>
>
> );
> }
> }
>
> class B extends Component {
> render() {
> return (
>
>
this is B component
>
>
> );
> }
> }
>
> // class C extends Component {
> // 此方法只能在类组件中使用
> // static contextType = nameContext
>
>
> // render() {
> // return (
> //
> //
this is C component 接收到祖组件传来的数据:{ this.context.name }
> //
> // );
> // }
> // }
>
> function C(){
> return (
>
>
this is C component 接收到祖组件传来的数据:
> {//此方法类组件和函数组件都可以使用}
>
> {
> value =>{
> return (
> { value.name }
> )
> }
> }
>
>
> );
> }
>
> export default A;
>
> ```
### 6. 组件优化
#### Component的2个问题
> 1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
>
> 2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
#### 效率高的做法
> 只有当组件的state或props数据发生改变时才重新render()
#### 原因
> Component中的shouldComponentUpdate()总是返回true
#### 解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
#### 例子
> ```jsx
> import React, { PureComponent } from "react";
> class A extends PureComponent {
> state = {
> name : '陶贼帅'
> }
> render() {
> return (
>
>
this is A component 传递数据:{this.state.name}
>
>
>
>
>
> );
> }
> }
> ```
>
>
### 7. render props
#### 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
#### children props
xxxx
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
#### render props
}>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
例子:
> ```jsx
> import React, { Component } from "react";
>
> class A extends Component {
> state = {
> name : '陶贼帅'
> }
> render() {
> return (
>
>
this is A component
> } name={ this.state.name }/>
>
>
> );
> }
> }
>
> class B extends Component {
> render() {
> return (
>
>
this is B component
> { this.props.render( this.props.name ) }
>
> );
> }
> }
>
> class C extends Component {
>
>
> render() {
> return (
>
>
this is C component 接收到参数:{ this.props.name }
>
> );
> }
> }
>
>
> export default A;
>
> ```
### 8. 错误边界
#### 理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
#### 特点:
只能捕获后代组件**生命周期**产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
##### 使用方式:
getDerivedStateFromError配合componentDidCatch
```js
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
```
### 9. 组件通信方式总结
#### 组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
#### 几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
#### 比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)