# 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 ( > > ) > } > } > > 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 ( > > ) > } > } > //对传递进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 ( > > ) > } > } > ``` > > 将组件相关的都放入类中,让其为静态变量,所有的实例对象只有一份。 ### (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)生命周期(旧) ![生命周期(旧)](E:\前端学习\react学习\react全家桶资料\02_原理图\react生命周期(旧).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

>
>
>
>
>
>
> {/* > About > > > Home > */} > about > home >
>
>
>
>
> > }> > }> > >
>
>
>
>
> ); > } > } > ``` ​ 产生原因:在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组件中 > > {/*此处路径前不加/*/} > > }> > }> > }> > > ``` **5.0版本** > ```jsx > //父组件中 > > > > > > > //Home组件中 > > {/*此处路径前不加/, 如果开启严格模式,则无法匹配子路由 */} > > > > > > ``` ### (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 原理图![](E:\前端学习\react学习\react全家桶资料\02_原理图\redux原理图.png) ### 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组件`只用于页面展示和容器组件交互数据。 ![](E:\前端学习\react学习\react全家桶资料\02_原理图\react-redux模型图.png) (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(开发用的少,封装插件用的多)