React入门学习笔记

180 阅读23分钟

标题:尚硅谷React教程(2022加更,B站超火react教程)_哔哩哔哩_bilibili

网址:www.bilibili.com/video/BV1wy…

React个人理解(B站尚硅谷)

第一章jsx

image.png

image.png

image.png

1.1 Hello,react示例(最简单的例子)

react.development.js是核心代码块 react-dom.development.js提供了操作dom的各种方法 babel.min.js可以将jsx转换成js image.png babel会解析script里的代码,当代码小时性能可以忽视,但当代码量大时就不得不重视这个问题

1.2 虚拟DOM的两种创建语法

如果想用js生成虚拟dom需要用到React.creatElement()方法,三个参数为标签、标签属性、标签内容。可以看出,如果使用js遇到嵌套会很麻烦,实际上jsx就是js的语法糖,方便我们创建虚拟dom。 image.png

1.3 虚拟DOM与真实DOM

image.png 这里用debugger是一种高级的调试方法,不然的话TDOM只会输出<div>xxx<div>的形式,当使用了debugger后就会生成断点,可以看到真实DOM对象的属性与方法。同时注意关键字typeof与instanceof的使用。

1.4 jsx语法规则

image.png 注意,内联样式中若原本有多个字符连接的属性例如font-size,在jsx中要用小驼峰的写法,例如fontSize。 在{}中只能写JS表达式(能用变量接受的就是表达式),区分表达式与语句! 想定义style要用{{}},最外层说明是里面是一个js表达式,里面那一层表示是一个对象,已达到键值对的效果。

image.png 遍历一个数组生成相应的li练习。注意{index}与{item}的利用,当想使用js中的变量时都需要使用{}。

补充:js的装杯写法

连续结构赋值:利用‘:’别名,可以拿到最里面的属性,但中间的对象拿不到 image.png

三元表达式套娃,以代替多次使用if判断 image.png

props的对象解构赋值传入,一定要学会!

todos.map(todo => {
  return <Item key={todo.id} {...todo} updateTodo={updateTodo} />

默认传参:用于需要初始化的函数

image.png

props中的解构赋值

注意: 这是es6中的对象的解构赋值,而不是jsx中特有的语法(特指const {age, ...fatherProps} = props)。

function Son(props) { 
 console.log(props) 
 return <div> hello,world </div> 
} 
function Father(props) { 
 const { age,...fatherProps } = props
 return <Son { ...fatherProps } /> 
}
function Index() {  
 const indexProps = { name:'alien', age:'28', mes:'let us learn React !' } 
 return <Father { ...indexProps } />
}

对象中的key为对象时的写法

注意name这个变量外面的中括号,如果想添加一个对象的key值为参数就得这样写,跟取对象时取变量名时要加中括号一样。

setValue=(name,value)=>{
      this.setState({
          formData:{
              ...this.state.formData,
              [name]:value
          }
      })
  }

交换两个值,可以用数组的解构赋值!

if(lenA < lenB) {       // 让nodeA为最长链表的头,lenA为其长度
    // 交换变量注意加 “分号” ,两个数组交换变量在同一个作用域下时
    // 如果不加分号,下面两条代码等同于一条代码: [nodeA, nodeB] = [lenB, lenA]
    [nodeA, nodeB] = [nodeB, nodeA];
    [lenA, lenB] = [lenB, lenA];
}

第二章React面向组件编程

2.1 组件与模块的概念

image.png

2.2 函数式组件与类式组件

React官方给出了这两种定义组件的方式。

image.png 当使用自定义组件时,首字母不能小写,在渲染时也要注意标签的闭合。函数式组件适合于简单组件的开发。这个函数是React帮忙调用的。this指向本来是window,但在babel编译后使用严格模式下this不允许指向window,故会指向undefined。

image.png 类定义组件适用于构建更加复杂的组件。这里没有使用constructor构造函数,后面会提到。类中定义中必须得有的两个东西——extends React.Compnent与render函数

2.3 对state的理解

state是简单组件与复杂组件区别之处。 state是组件实例的三大属性之一。

例子 点一下从‘炎热’变成‘凉爽’

初始化state image.png

React中的事件绑定,在标签中写明事件并写上回调函数。 React将所有的事件都重写了一遍,将onclick变成了onClick,onblur改为了onBlur。这种写法有问题,demo写在类外面,拿不到组件实例中的state属性,因此要将demo函数定义在类中。 image.png

注意!类中的方法局部开启了严格模式,如果将一个类中的方法赋予给一个函数,该函数直接调用,则会变成undefined(严格模式下this不指向window) 在该案例中onClick中函数就是被赋值了一个类方法,因此changeWeather是一个回调函数,不是由组件调用的,而是直接调用的,点击后显示的还是undefined。 image.png

利用bind方法改变this的指向(bind方法会返回一个新函数)。在constructor中定义一个方法,并且constructor中的this指向永远指向实例对象。由于在原型链上有changeWeather该方法,因此不会报错。当触发onClick事件时,会触发实例上的方法changeWeather,从而拿到state属性。 当然还有一种解决方法是用箭头函数。 image.png

必须使用setState方法来改变state的属性,因为state属性是一个响应式数据。这点与微信小程序很像,每次调用一次该方法都会使render函数重新再渲染一次。 image.png

在类中一个变量直接赋值,会成为该对象的成员变量,用一个箭头函数赋值给一个变量,该成员变量的值是一个函数,该函数可以看成是实例的成员方法。下面是精简版的写法,也是之后一直写的版本。 image.png

state的总结 image.png

2.4 对props的理解

与在Vue中的props一样,props是标签属性,可以用于父向子传值。props是组件实例的三大属性之一。 以下是一个小例子: image.png

react中引入了批量传入props的方法{...obj},然而这种写法不是对对象的复制(es6中的扩展运算符不能展开对象),这里的{}表示其中使用的是js表达式,这种扩展对象是react与babel一起作用的结果,并且只能在对象中使用。当传入的是一个对象且子组件需要用到对象中的属性时使用! image.png

为了对props进行限制,引入了prop-type这个库,从而添加了PropTypes这一全局变量。 image.png

在这里注意func的写法,number、string没有与内置的Number、String,而function与关键字function冲突了,使用要用func。同时上面的propTypes是小写的,而PropType全局类是大写的。 image.png

可以看出以上代码是给Person类添加了两个静态属性propTypes与defaultProps,因此可以简写,用static。 image.png

注意,props是只读的!

React官网上对构造函数的定义 可以看出state的简写就可以了,不怎么需要构造器。 image.png 如果不传在构造器里面this.props就接受不到(虽然直接用props就好),不过基本不用构造器。 image.png

函数式组件使用不了state与refs,但可以接收props,因为函数式组件可以接收参数。利用解构赋值拿到传入的props对象。如果想对props进行限制,只能写在函数体外对Person进行定义了。 image.png

补充:子传父与父传子

在React中,子传父是通过props中的回调函数实现的(父向子的props中写入一个回调函数),子组件中再定义一个处理函数,在处理函数中调用回调函数(有时还需要函数的柯里化),对比Vue中的通过自定义事件来实现子传父,更加方便一些。

在React中,通过消息订阅与交互来实现兄弟组件的交互(也适用于任意的组件间沟通)。订阅者确定一个订阅名与一个回调函数并生成一个token(用于取消订阅),当有一个组件发布了这个订阅名后就会触发这个回调函数。推荐使用PubSubJS库作为发布与订阅的包,下图为例: image.png image.png image.png image.png 消息的订阅与发布类似于Vue中的全局事件总线,但不需要自定义事件,但要引入外部的包。同时在组件销毁的时候注意清除消息订阅。

2.5 对ref的理解

组件内的标签可以用ref来标识自己。 拿到的ref对应的对象不是虚拟DOM,而是真实的DOM。

字符串形式的ref(同理,利用解构赋值拿取值),现在字符串ref已经不怎么用了,逐渐被react废弃(效率不高)。 image.png

函数类型的ref 当创建结点时,遇见ref,会触发其中的回调函数,c为当前结点,例如该例子中就是<input>....</input>这个标签结点。只有ref属性才会触发回调函数。 image.png

函数触发的次数 image.png

解决方法,虽然没什么大的影响,一般还是写普通的回调。 image.png

CreateRef创造Ref

React.createRef调用的容器如果被俩个人用会被覆盖。例子中this.myRef中存的是{current: input},想拿到input结点还需要使用this.myRef.current。这种写法是React最推荐的,但可以看出很麻烦,而且一旦ref多了的话要创建很多个容器。 image.png

不要过度使用ref,能用event.target就用。 image.png

2.6 React中的事件处理

image.png event.target得到数据,类似于小程序。第二点的意思就是冒泡。

标题:尚硅谷React教程(2022加更,B站超火react教程)_哔哩哔哩_bilibili

网址:www.bilibili.com/video/BV1wy…

2.7 受控组件与非受控组件

页面上所有输入型的组件,现有现取的组件都是非受控组件。点击提交后会默认跳转到action的网址,想阻止这种跳转,需要阻止默认事件的发生。 image.png

受控型组可以理解为vue的双向绑定v-model。一般都推荐写受控组件。 image.png

补充:高阶函数与函数柯里化

image.png

利用高阶函数与函数柯里化实现用一个函数接收两个改变的数值 image.png

不使用高阶函数与函数柯里化的写法(两种方法都要掌握!) image.png

第三章 生命周期

3.1 生命周期(旧)

生命周期钩子函数实例(卸载组件用的是ReactDOM.unmountComponentAtNode(容器)) image.png

生命周期旧版流程图 image.png 主要是右侧的几条线,shouldComponentUpdate钩子会默认返回一个布尔值,如果为真则放行,为假就禁止通行。强制更新是指不更改任何数据也想让组件进行渲染,使用this.forceUpdate()。this指向组件实例。

如果父子件状态改变时,其中的子组件会经历componentWillReciveProps这一条线(第一次不算)。以下为具体的一个示例。 image.png

旧版生命周期总结 componentDidMount比较常用,做初始化工作,例如网络请求,订阅,或者定时器。componentWillUnmount也比较常用,做结尾工作,例如关闭定时器、关闭订阅。 image.png

3.2 生命周期(新)

componentWillUpdate,componentWillMount,componentWillReceiveProps基本弃用(虽然基本也用不上),新增了getDerivedStateFromProps与getSnapshotBeforeUpdate两个生命周期钩子,当然这两个也蛮罕见的。

生命周期新版流程图 image.png

getDerivedStateFromProps函数是一个类函数(要加static),返回一个状态对象或者null,不能返回undefined。可以接受两个参数props与state。此函数用于一个很特殊的用例,当state任何时候都取决于props时可以使用,用了之后还会使代码冗余。)注意:前面必须要写static! image.png

getSnapshotBeforeUpdate函数必须返回一个null或者快照值(任何数据结构都可以作为快照值),此函数会将快照值传给componentDidUpdate函数。 image.png

componentDidUpdate函数可以接受三个参数,preProps,preState,snapshotValue。 image.png

具体例子,使鼠标移动滚动条后滚动条静止不动。

class NewsScroll extends React.Component {
      
      state = { news: [] }

      componentDidMount() {
        setInterval(() => {
          const news = this.state.news;
          const new_item = '新闻' + (news.length + 1);
          this.setState({news: [new_item, ...news]});
        }, 1000)
      }

      getSnapshotBeforeUpdate() {
        return this.refs.list.scrollHeight;
      }

      componentDidUpdate(preProps, preState, height) {
        this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
      }

      render() {
        return (
            <div ref='list' className='ul'>
              { 
                this.state.news.map((item, index) => {
                  return <div key={index} className='li'>{item}</div>
                })
              }
            </div>
        )
      }
    }

    ReactDOM.render(<NewsScroll />, document.getElementById('test'));

呈现效果如图显示。 image.png

补充:Diffing算法中key的作用

image.png image.png

补充:脚手架文件的配置

image.png

第四章 React拓展

4.1 setState

React状态的变化是一个异步的,回调函数会在异步状态更新完后执行。 image.png

函数式setState与对象式setState举例 image.png

4.2 LazyLoad(听完路由后还要回来总结)

当项目过大时需要使用懒加载,在路由中用的最多。如果不用懒加载,那么如果有100个路由,在项目一开始就会加载,但如果使用了懒加载,用户点那个就会跳转那个,节省了资源。当然,懒加载在图片中应用最为实用! image.png

4.3 Hooks

钩子,作用是是函数式组件能用类式组件的各个功能。 image.png

4.3.1 StateHook

第一次进入是React.useState会执行,初始化想要初始化的数据,但当更新的时候就会不会再初始化,如图所示,第二次就会渲染就会自增,count会加一。 image.png 每次想让state中多一个属性,就要使用一次React.useState。 image.png 注意,如果变化的数据需要之前的state,要用函数式!例如:

let timer = setInterval(() => {
      setCount(count => count+1);
    }, 1000)

这里count本来是0,如果用setCount(count+1),那么永远会变成1,要想实现自增效果,必须是count => count+1也就是说,使用setXxx(newValue),其中的newValue如果有表达式,会将初始的变量表达式的值作为newValue的值,之后表达式中的变量的变化也不会影响newValue的值,换言之newValue在第一次初始化时值就确定了。

4.3.2 EffectHook

EffectHook的设计就是使函数式组件能够实现类式组件生命周期的效果。 []中写哪一个属性就是对state的哪一个属性进行监视,实现了componentDidUpdate的效果。类似于Vue中的watch监视器。 image.png 利用EffectHook实现类式组件中利用componentDidMount创建定时器,利用componentWillUnmount消除定时器。(注意,在脚手架中不能使用unmountComponentAtNode来移除root,因为在index.js中const root = ReactDOM.createRoot(document.getElementById('root'));,想要删除root根组件要使用root.unmount(),index.js需要向外暴露root) image.png

4.3.3 RefHook

构建一个容器,createRef类似。利用React.useRef()方法来创建ref容器。 image.png

4.4 Fragment

跟template一样的功能,包裹多层结构,可以不用div了 可以传入一个key值,方便循环遍历,例:<Fragment key={1}></Fragment> image.png

4.5 Context

祖组件与后代组件是至少隔一辈的组件。 image.png

image.png 以A,B,C三层为例: image.png 包裹在Provider中的组件都能接受到信息,但想接收的话必须先声明,如下图所示: image.png 在类中的写法如图所示: image.png

4.6 PureComponent

纵使子组件一点没有父组件的数据,父组件重新render也会使子组件render。原因是生命周期钩子shouldComponentUpdate的返回值永远是true。 解决方法一: 该生命周期钩子可以传入两个参数之后的state与之后的props,从而可以与this.state和this.props进行比较,以此来判断props与state有没有发生变化,进而决定是否刷新。在子组件中也是如此,通过判断父组件传入的props是否变化来决定是否刷新。但一旦state属性多了就要一个一个遍历。 开发时不控制阀门,利用PureComponent。 image.png

解决办法二: 利用PureComponent,例如class Chlid extends PureComponent 实际上PureComponent做了一次浅对比,如果前后两个对象的地址一样,就不会刷新,如图所示,注意注释掉的数组与新写的数组添加的区别,新写的对象改变与注释掉的区别。 image.png

总结: image.png

4.7 renderProps

另一种形成父子组件的办法:在A组件中包裹B,B为A的标签体,存在A的.props.children里(标签体都存在这里),但此时不能向B传props。标签体是一种特殊的标签属性,例如<xx children="XXX"></xx>其中的children就是标签体 image.png

实际上,props.children可以是任意东西,类似于插槽,因此可以在A组件的标签上定义render函数,自己设置传入什么值。比如把下图中的B组件换成别的组件就能将其替换。 image.png

总结: image.png

4.8 错误边界

将错误控制在一定范围内,例如子组件出错,父组件能正常展示。(要在子组件的父组件上定义错误边界) 在父组件中定义错误边界实例(在开发环境没啥用,在build之后才有用)。 image.png 注意:只能捕获生命周期产生的错误! image.png

4.9 组件间通信方式总结

image.png

第五章 React中的Ajax

5.1 配置代理

在React中还是使用axios来发送Ajax请求,轻量且方便。

浏览器环境使用Ajax引擎,存在同源策略不允许跨域。如果本地3000端口去向5000端口的服务器请求数据,会发出请求,但响应会被Ajax拦截。但服务器就没有同源策略,3000端口的服务器与5000端口的服务器可以互相响应,因此可以利用代理来解决方法。脚手架中的Webpack配了一个devSevrer,可以开一个服务器模拟项目上线。其中public这个文件夹就是该服务器的根路径,如果找不到资源并且没有配置代理,默认返回index.html

方法一,在package.json中配置proxy image.png 浏览器向3000端口的服务器发送信息,3000端口的服务器会先在自己的目录下寻找(也就是react中的public目录),如果寻找不到再向5000端口发送请求。缺点是只能配置一个代理。

方法二,在src中创建代理配置文件setupProxy.js

const {createProxyMiddleware} = require('http-proxy-middleware')
module.exports = function(app) {
  app.use(
    createProxyMiddleware('/api1',{ //遇见/apil前缀的请求就会触发
      target: 'http://localhost:5000', //配置转发目标地址
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值,改变发送请求的源目的
      pathRewrite: {'^/api1': ''} //去除请求前缀址(必须配置)
    }),
    createProxyMiddleware('/api2', { 
      target: 'http://localhost:5001', 
      changeOrigin: true, 
      pathRewrite: {'^/api1': ''}
    })
  )
}

对changeOrigin的解释: image.png 这个文件只能叫这个名字,并且只能用CommonJS写(不能用es6),脚手架启动时webpack会自动执行这个文件。

5.2 fetch()

axios与jQuery都是对xhr的封装,除了xhr之外,浏览器自带的另一个发送请求的方式为fetch()。当然,fetch方法不适合老版本的浏览器,fetch设计不仅使用了promise链式调用的思想,还遵循了关注分离的思想(即将复杂问题细分化,先联系服务器,再从服务器中获取数据,也就是两个Promise)。 image.png 第一次向服务器发送请求,回来的response要使用.json()方法,变成一个pending状态的Promise,如果成功就会存有成功的对象。失败就会有失败的原因。 image.png fetch链式调用,前面是联系服务器是否成功,后面是服务器获取资源是否成功,注意:连续调用Promise时就算失败了返回非Promise,也会进入下一状态的成功回调,因此要返回一个空的Promise停止,当然也可以不写失败的回调,直接最后.catch兜底统一处理错误以上是未优化的方法。 image.png 利用async/await进行优化(看到Promise就要想到async/await优化,以写出更优美的代码),注意要在包裹await的函数中前面加上async。

第六章 React中的路由(旧)

image.png 我们使用的库为插件库react-router中的react-router-dom(web专用)。

6.1 基本使用

image.png 在需要使用Link与Router的外面包一层BrowserRouter(link和router不能在不同的BrowserRouter中) image.png 在APP组件中定义Link与Router,实现路由的编写与跳转

基本步骤: 1.引入Route与Link,在APP组件中引入BrowserRouter或者HashRouter 2. 确定界面中的导航区与展示区 3. 导航区加上Link标签<Link to='/xxx'></Link> 4. 展示区进行路由匹配<Route path='/xxx' component={Xxx}></Route> 5. 在<App>组件外面包一层<BrowserRouter><HashRouter>

6.2 路由组件与普通组件的区别

image.png

image.png

6.3 NavLink

普通Link点了之后不会改变类名,用了NavLink之后点了之后就会变高亮(其实会默认加一个active类名),可以设置属性‘activeClassName’,从而改变添加的类名。 image.png 自己封装NavLink组件,{...this.props}不仅把普通的props传了过去,还把标签体children也传了过去,标签体是一种特殊的标签内容 image.png

image.png

6.4 Switch的使用

image.png 如果不包Switch,路由是以最后匹配的为主,当包了Switch,则是以最先注册的组件为主。

6.5 模糊匹配与精准匹配

image.png 最左前缀匹配原则,默认开启模糊匹配,如图也可以跳转。想要开启精准匹配,需要在<Route>中加入exact image.png 并不是严格匹配就一定好,当路由很多很复杂时可能不会匹配二级路由。当实在需要时再开精准匹配(一般不用开启)。

6.6 Redirect的使用

和Vue中的一样,Redirect的作用是重定向。 image.png Redirect写在所有的路由组件最下方,所有路由都不匹配就跳转Redirect,兜底的作用(不需要和Switch组件一起用)。

6.7 嵌套路由

React的路由是有先后注册顺序的,会先匹配'/home',进入Home组件,再在组件中匹配'/news',进入News组件。下面是Home组件中二级路由的代码实例,注意二级路由必须写成'/home/news',不能写成'/news',这样就匹配不上Home组件了,因为路由的匹配是按路由注册顺序进行的。

image.png 如果开启严格模式,'/home/news'与'/home'不匹配,无法达到嵌套子路由的匹配,也就使其无法发挥作用了。

6.8 路由组件传参

案例:现有一个三级路由,点击消息1显示消息1的列表,点击消息2显示消息2的列表,消息3同理,由于下方的组件模式是一样的,因此可以设置一个通用的组件,将传入的数据进行渲染即可,也就是说,想完成这个案例,需要知晓组件之间如何传参。 image.png

方法一、利用params传递参数 注意模板字符串必须在js中使用,故要在外面加一层{},同时注意在挂载时path中接收参数的写法/:id/:title,这就是需要声明接受的过程。 image.png

在路由组件中的props中的match中的params中存储着需要的数据。 image.png

Detial组件的代码实例,注意在红框内find方法的写法,同样是过滤,find方法找到第一个就停止寻找,而filter方法会找到所有符合条件的数组项(会遍历一遍数组),在这里使用find效率更高。 image.png

方法二、利用search search方法不需要声明接收,在路径上用query方式即可。 image.png

可以看出search参数是一个字符串,想使用的话就要将其转为一个对象 image.png

引入‘qs’库(React自带的),将urlencoded方式的编码转换为对象。 image.png

注意要将最前面的'?'去掉,可以用slice方法。 image.png

方法三、利用state方法传递参数 如果不使用这种方法传递参数,则location中的state默认为undefined。state方法不需要声明接收,但不能只设置pathname,并且要将pathmane设置在一个对象中并且传递一个state属性,并且用这种方法并不会暴露传递的参数(前面两种通过看地址栏就能看到)。注意:该state与组件的三大属性之一的state只是同名而已,没有一点关系。 image.png

同理,用this.props.location.state可以拿到传递过来的state对象。 image.png

注意,state是存在浏览器的history里面的,如果清除浏览器的缓存,那么state就会变成undefined,此时就会报错。为了使其不报错,在取state对象的时候就得像下面这行代码一样或上一个空的对象。 image.png

总结: image.png

6.9 路由的replace模式

一般默认是push模式,在标签体中加上replace属性就是开启replace模式,登录后的跳转就可以用这种模式。 image.png

6.10 编程式导航

所谓编程式导航,就是用户不点击路由组件通过代码调用实现跳转,例如连接出错之后过了多少多少秒之后自动跳转。 image.png 在路由组件的props的history属性中有push函数与replace函数,可以调用这两个函数实现编程式导航,下面写了两个button,并且定义了跳转的回调函数来实现push与replace,同样地,还可以实现前与后退。 image.png image.png

实现前进于后退,go函数也同理,n表示前进的页数,如果是负数就是后退的页数。 image.png

总结: image.png

6.11 withRouter

一般组件没有路由组件的api,要想让一般组件可以使用路由组件的api,得使用withRouter函数。 image.png

6.12 HashRouter与BrowserRouter的区别

解决路径错误问题,例如之前提到的样式错误。HashRouter没有使用h5新增的history,因此在使用state传参的时候会出现错误。 image.png

第七章 React中的路由(新)

7.1 Route6与Route5的区别(Routes与Navigate详解,NavLink高亮)

image.png

在Link地方变的地方不多,但在Route那边必须将所有的Route都包在Routes里面,不然会报错,注册的话是与Switch一样,哪个组件先注册了到时候就显示的是哪个组件,路径默认不分大小写。 image.png 重定向不再使用RedirectTo组件,使用Navigate组件,也需要引入,使用时如下图显示。 image.png image.png Navigate在组件内的使用,判断是否满足条件,如果满足就跳转(编程式导航)。 image.png

image.png

NavLink高亮 在之前直接设置一个activeClassName即可,现在要给className传一个函数,该函数有一个默认的传的对象,其属性为isActive,该属性当被选了就会变成true,不然就是false。函数返回值为该NavLink的所有类名。相当于Router6取消了activeClassName这个属性,通过点击时传的对象{isActive}来判断当前组件是否被选中。 image.png

父组件可以写一个end属性,使当子组件显示时子组件的NavLink标签高亮而父组件的不高亮(即isActive变为false) image.png

7.2 useRoutes

通过useRoutes(需要引入)创建了路由表,路由表可以代替之前的Route组件来注册路由。以下为实例 image.png image.png 一般会在src中新建一个routes文件夹创建全局的路由表并对外开放,其他组件使用时引入即可。

同时,不需要全部写路径了,但不能写斜杠,不然会自动匹配。 image.png

7.3 嵌套路由

在一个父路由下方设置children属性,该属性是一个路由数组。 image.png

当引入路由表时,由于没Routes,因此是不知道路由是在哪里展示的,这时候就需要导入Outlet(有点像vue中的route-view) image.png

7.4 路由传参(三种方式)

方法一、params传参

传参时与Route5差不多 image.png image.png

但在接收参数时需要使用相关的hook(引入useParams或者useMatch,一般用useParams,useMatch太麻烦了) image.png

方法二、search传参

传参时与Route5差不多 image.png

接收参数时需要引入useSearchParams(使用方法类似于useState,使用数组解构拿到search与setSearch),具体实现形式如下,值得注意的是拿到的search参数是一个响应式对象,想拿到就需要用get方法(同理也可以用useLocation,也很麻烦,因为search是字符串形式的)。 image.png

方法三、state传参

在传参时比Route5方便了很多,不需要将路径名与state一起包装起来了。 image.png

在接收参数时需要引入useLocation image.png

7.5 编程式导航

需要使用useNavigate这个钩子函数。可以传两个参数,第一个是路径参数,另一个是配置对象,目前只支持replace与state。 image.png

在Router5中,想要让一个组件使用路由组件的API需要使用withRouter,而在Router6中没有这个函数,因为一般组件也可以使用useNavigate这个钩子函数实现页面的跳转。 image.png

一下四个不怎么常用但应该了解

7.6 useInRouterContext()

如果组件处在<Route>组件中,则返回true,否则返回false

由于App组件被<BrowserRouter>包裹,因此在App组件及其子组件中调用时一直返回true image.png

7.7 useNavigationType()

返回当前的导航类型(pop、push、replace) pop是指当前页面直接刷新 image.png image.png

7.8 useOutlet()

用来呈现当前组件所渲染的嵌套路由。 image.png

image.png

7.9 useResolvedPath()

给定一个URL值,解析其中的search、path、hash值。 image.png image.png

第八章 UI组件库Ant-Design

8.1 antd的基本使用

进入ant.design/index-cn这个官方网址,自己看文档并引入组件代码,例如:

import React from 'react'; 
import { Button, Space } from 'antd'; 
const App: React.FC = () => ( 
    <Space wrap>
        <Button type="primary">Primary Button</Button> 
        <Button>Default Button</Button>
        <Button type="dashed">Dashed Button</Button>
        <Button type="text">Text Button</Button> 
        <Button type="link">Link Button</Button> 
    </Space> ); 
export default App;

这就是示例的代码,就可以引入项目之中。但引入后是没有css样式的,需要去antd这个包中引入css,在需要使用antd组件中写import 'antd/dist/reset.css',这样使用的antd组件才有完整的样式。

8.2 按需使用高级设置

见antd官网选择3.x的版本中的‘在create-reate-app使用’中的高级配置(已经过时了现在)。

第九章 redux

9.1 redux简介

redux是一个专门做状态管理的js库。 作用:集中式管理react多个组件共享的状态。

什么时候使用redux?

  1. 某个组件的状态要让别的组件随时拿到(共享)
  2. 一个组件想要改变其他组件的状态(通信)
  3. 总体原则:能不用就不用,如果不用的话感觉比较吃力再使用。

9.2 redux工作流程

image.png

redux的三个核心概念: image.png

9.3 react-redux基本使用

image.png

facebook看那么多人用redux,就自己出了这么一个库。核心原理图: image.png

同时,store不是在容器创建文件里引入的,而是在Count组件使用时传入一个属性store。 image.png

容器组件需要react-redux生成,不能自己创造。通过connect()()创建一个容器组件并向外暴露,第一个括号内可以传两个参数,react在创建组件时会调用这两个参数,这两个参数都必须是函数,其中第一个函数返回一个对象,其中的键值对作为UI组件的props值(给UI组件传状态),另一个函数返回方法(给UI组件传操作状态的方法)。注意:这里的connect函数是一个柯里化函数,connect返回一个函数,将CountUI这个UI组件作为传参传给这个返回的函数。实际上,可以理解为这个容器组件中就有store的相关api,因此在两个函数中可以传入state与dispatch,二者皆为store中的api

// 引入所需包裹的UI组件
import CountUI from '../../components/Count/Count'
// 引入connect函数
import {connect} from 'react-redux'
// 引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action'

// 通过mapStateToProps函数向UI组件传输状态
function mapStateToProps(state) {
  return {count: state}
}

// 通过mapDispatchToProps函数向UI组件传输改变状态的方法
function mapDispatchToProps(dispatch) {
  return {
    increment: data => { dispatch(createIncrementAction(data))},
    decrement: data => { dispatch(createDecrementAction(data))},
    incrementAsync: (data, time) => { dispatch(createIncrementAsyncAction(data, time))}
  }
}
// 通过connect()()创建一个容器组件并向外暴露
// 注意:这里的connect函数是一个柯里化函数,connect返回一个函数,将CountUI这个UI组件作为传参传给这个返回的函数
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
mapDispatchToProps简写

mapDispatchToProps可以传两种参数,一种是普通函数,另一种是对象。只要向子组件UI传一个函数,当其被调用时,默认会调用dispatch。

export default connect(
  (state) => ({count: state}), 
  // mapDispatchToProps的一般写法
  // (dispatch) => ({
  //   increment: data => { dispatch(createIncrementAction(data))},
  //   decrement: data => { dispatch(createDecrementAction(data))},
  //   incrementAsync: (data, time) => { dispatch(createIncrementAsyncAction(data, time))}
  // })

  // mapDispatchToProps的简写
  {
    increment: createIncrementAction,
    decrement: createDecrementAction,
    incrementAsync: createIncrementAsyncAction
  }
)(CountUI)

9.4 Provider组件的使用

当使用react-redux时,就不需要在index.js中调用store.subscribe()来不断监视store的变化了,connect函数帮助我们实习了对于state的监听。观察核心原理图,UI组件中的数据改变是props传参的,因此不需要使用store.subscribe()进行监听。

如果有很多组件需要使用store,可以使用Provider,Provider会精准地分析哪些组件需要store,因此只要其包裹组件即可。

9.5 容器组件与UI组件的整合

见练习代码,实际就是将component中的组件整合到container中的容器组件之中。

补充:纯函数

纯函数的定义:无论何时输入相同的数据(实参),总能得到同样的输出(返回值)。纯函数的几个约束:1. 不得改写参数的数据(例如传入a=9,不能在其中更改a的值)。 2. 不会产生如何副作用,例如网络请求、输入输出设备(因为这些状态可能会改变)。 3. 不能使用Math.random()与Date.now()等方法。

redux的Reducer必须是一个纯函数。