深度学习React相关技术栈

之前,学习过React,但是2、3个月没用的话,就是忘得差不多了,在最近的这段时间里发现大厂用React技术栈很多,然后就特意去温习了一下,整个感觉就是React挺吃JS的,国庆期间然后卷了一波,成为他们口中所说的“卷王!”
在这里插入图片描述

React全家桶

一、初识React

首先需要引入React库,react.development.js需要在react-dom.development.js之前引入,因为react.development.js是核心库。

在这里插入图片描述

二、jsx的语法编写规则

  1. 定义虚拟DOM时,不要写引号
  2. 标签中混入JS表达式要用{}包起来
  3. 标签内样式的类名指定不能使用class,得用className
  4. 在标签内写内联样式,要用style={{color:'red',fontSize:'12px'}}形式去写
  5. 虚拟DOM只有一个根标签
  6. 所有的标签必须闭合
  7. 标签的首字母
    • 若小写字母开头,则将该标签转为html中同名的元素。没有同名元素会报错
    • 若大写字母开头,React就会去渲染对应的组件。如果组件没有定义,则就会去报错

如果想要写判断条件,可以写成三元表达式,三元表达式可以嵌套使用

2.1 区分js语句代码与js表达式

表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式

  • a
  • a+b
  • main()
  • arr.map()
  • function test(){}

语句代码:下面这些都是语句代码

  • if(){}
  • for(){}
  • switch(){}

三、React中的组件

3.1 函数式组件

在这里插入图片描述

3.2 类式组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1wtGQDg-1632465085212)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210313153136831.png)]

3.3 组件实例的三大核心属性

3.3.1 state
  1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  2. 通过更新组件的state来更新对应的页面显示(重新渲染组件)

注:

1. 组件中render方法中的this为组件实例对象

2. 组件自定义方法中的this为undefined,如何解决?

  • 强制绑定this:通过函数对象的bind()
  • 使用箭头函数

3. 状态数据,不能直接修改,得通过setState来对数据进行更新

state的基本使用

在这里插入图片描述

state的简写方式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMAkMXW3-1632465085219)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210314150257561.png)]

类组件中在创建之后发生了什么?

  1. 首先需要在类组件内部直接初始化state的状态
  2. 接着在render()函数内部定义常量,拿到state中的数据后,return出需要展示的内容
  3. 在该类组件内部可以自定义许多方法,但是为了使方法挂载到该类的实例上,需要用 赋值语句+箭头函数的形式来自定义方法,从而避免this指向丢失的问题

最后,采用ReactDOM.render()的方式,将组件渲染到页面上。

3.3.2 props
  1. props是每个组件对象都会有的属性
  2. 组件标签的所有属性都保存在props中

作用:

  • 通过标签属性从组件外向组件内传递变化的数据
  • 组件内部不要修改props数据

props的基本使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7tZ6kJQO-1632465085222)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210314154942069.png)]

对props进行限制

在脚手架对props进行限制时,首先需要npm i prop-types,然后在子组件中引入,

然后需要用static关键字声明,之后对其接收到的数据进行限制

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- 准备容器 -->
  <div id="test1"></div>
  <div id="test2"></div>
  <div id="test3"></div>

  <!-- 引入React核心库 -->
  <script src="../../00-React库/react.development.js"></script>
  <!-- 引入React.dom库 -->
  <script src="../../00-React库/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../../00-React库/babel.min.js"></script>
  <!-- 引入prop-type,来对props进行限制 -->  
  <script src="../../00-React库/prop-types.js"></script>

  <!-- 需求 -->
  <!-- 
      将要展示的年龄在原来的基础上+1
      姓名限制为必填
      性别限制为字符串
      年龄限制为数字
   -->
  <script type="text/babel">
    // 创建组件
    class Person extends React.Component{
      render(){
        console.log(this)
        const {name,age,sex} = this.props
        // props是只读的,不可以修改
        // this.props.name = 'aaa' 此行代码会报错
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age+1}</li>
            <li>性别:{sex}</li>
          </ul>
        )
      }
    }
    // 对标签属性进行类型、必要性的限制
    Person.propTypes = {
      name:PropTypes.string.isRequired, //限制name为必传项,且为字符串
      age:PropTypes.number, //限制age为数字
      sex:PropTypes.string, //限制sex为字符串
      speak:PropTypes.func, //限制speak为函数
    }

    // 指定属性的默认值
    Person.defaultProps = {
      sex:'男',//sex默认值为男
      age:18 //age默认值为18
    }
    // 渲染组件
    ReactDOM.render(<Person name="李白" age={18} sex="男"/>,document.getElementById('test1'))
    ReactDOM.render(<Person name='杜甫'  sex="男"/>,document.getElementById('test2'))
    // ReactDOM.render(<Person name="貂蝉" age="90" sex="女"/>,document.getElementById('test3'))
    // 批量传递props
    const p = {name:'宋庆扬',age:20,sex:'女'}
    ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
	
	function speak() {
    	console.log('我说话了')
    }
  </script>
</body>
</html>
props的简写方式

在该类组件的内部,在需要添加的属性前面加上static关键字,即可给类自身添加属性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 准备容器 -->
  <div id="test1"></div>
  <div id="test2"></div>
  <div id="test3"></div>

  <!-- 引入React核心库 -->
  <script src="../../00-React库/react.development.js"></script>
  <!-- 引入React.dom库 -->
  <script src="../../00-React库/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../../00-React库/babel.min.js"></script>
  <!-- 引入prop-type,来对props进行限制 -->
  <script src="../../00-React库/prop-types.js"></script>

  <!-- 需求 -->
  <!-- 
      将要展示的年龄在原来的基础上+1
      姓名限制为必填
      性别限制为字符串
      年龄限制为数字
   -->
  <script type="text/babel">
    // 创建组件
    class Person extends React.Component {
      // 对标签属性进行类型、必要性的限制
      static propTypes = {
        name: PropTypes.string.isRequired, //限制name为必传项,且为字符串
        age: PropTypes.number, //限制age为数字
        sex: PropTypes.string, //限制sex为字符串
        speak: PropTypes.func, //限制speak为函数
      }

      // 指定属性的默认值
      static defaultProps = {
        sex: '男',//sex默认值为男
        age: 18 //age默认值为18
      }
      
      render() {
        console.log(this)
        const { name, age, sex } = this.props
        // props是只读的,不可以修改
        // this.props.name = 'aaa' 此行代码会报错
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age + 1}</li>
            <li>性别:{sex}</li>
          </ul>
        )
      }
    }
    // 渲染组件
    ReactDOM.render(<Person name="李白" age={18} sex="男" speak={speak} />, document.getElementById('test1'))
    ReactDOM.render(<Person name='杜甫' sex="男" />, document.getElementById('test2'))
    // ReactDOM.render(<Person name="貂蝉" age="90" sex="女"/>,document.getElementById('test3'))
    // 批量传递props
    const p = { name: '宋庆扬', age: 20, sex: '女' }
    ReactDOM.render(<Person {...p} />, document.getElementById('test3'))

    function speak() {
      console.log('我说话了')
    }
  </script>
</body>

</html>
3.3.3 refs与事件处理

理解:组件内的标签可以定义ref属性来标识自己

字符串形式的ref

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4L4uym8w-1632465085224)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210315163007772.png)]

回调的ref

ref直接接收一个回调,该回调的参数就是该元素的节点,随后起一个常量名用来接收该节点。

在这里插入图片描述

ref回调次数的问题

ref方式数的方式定义的,在更新的过程中它会被执行两次,第一次传入的参数为null,第二次传入的参数才是dom元素。这是因为在每次渲染时会创建一个新的函数实例,React会清空旧的ref然后再设置一个新的。

解决方案

将ref的回调函数定义成一个class的绑定函数的方式可以避免以上问题,但是写成内联函数的方式也无关紧要

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vg0LNn9Z-1632465085227)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210315194857987.png)]

创建Ref容器

this.ref2.current指代的就是当前ref2所指向的根节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vsG1k2tW-1632465085228)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210315200705470.png)]

ref中的事件处理
  1. 通过onXxx属性指定事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件,而不是使用的原生DOM事件—–为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)—–为了更加高效
  2. 通过event.target得到发生事件的DOM元素对象—–不要过度使用ref
  3. 即使在某一个根元素中不传入ref,也可以发生回调事件,前提是只能发生在某个根节点上的自身的事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bN4soTlo-1632465085229)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210315202551541.png)]

四、React如何将输入的值同步更新到状态中

首先输入类的DOM标签中都包含着一个onChage回调,然后可以在这个回调中使用setState()来改变state中的数据,与Vue数据双向绑定原理类似

<body>
  <script src="../../00-React库/react.development.js"></script>
  <script src="../../00-React库/react-dom.development.js"></script>
  <script src="../../00-React库/babel.min.js"></script>
  <script src="../../00-React库/prop-types.js"></script>

  <div id="box"></div>
  <script type="text/babel">
    // 1. 创建组件
    class Login extends React.Component {
      state = {
        username:'',
        password:''
      }

      submit = (event)=>{
        event.preventDefault();
        const {username,password} = this.state
        alert(`您输入的用户名是${ username},密码是${ password}`)
      }

      // 保存用户名到状态中
      saveUsername = (event)=>{
        this.setState({
          username:event.target.value
        })
      }
      // 保存密码到状态中
      savePassword = (event)=>{
        this.setState({
          password:event.target.value
        })
      }
      render() {
        return (
          <form onSubmit={this.submit}>
            用户名:<input type="text" onChange={this.saveUsername} name="username" />
            密码:<input type="text" onChange={this.savePassword} name="password" />
            <button>提交</button>
          </form>
        )
      }
    }
    // 2. 渲染组件
    ReactDOM.render(<Login/>,document.getElementById('box'))
  </script>
</body>

五、React生命周期

旧的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PO9sWdIz-1632465085231)(D:\fore-end\React\08-react的生命周期函数\02-react生命周期(旧)].png)

卸载组件的方法为:ReactDOM.unmountComponentAtNode(document.getElementById('box'))}

组件挂载时,按照图中的顺序依次执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3e0vthUN-1632465085232)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210917103129537.png)]

当组件更新时

  1. 当组件走setState那条线时,shouldComponentUpdate默认返回true,它是组件更新的总开关,如果它返回的是false,组件将无法更新,那么之后的回调也将无法继续执行,程序只能执行到阀门这块
  2. 当组件走forceUpdate这条线时,意味着不改变状态而强制更新,需要调用forceUpdate()这个方法,他可以不受阀门控制,直接触发下面的回调钩子
  3. 当组件走最上方那条线时,父组件向子组件标签内传递了数据,那么将会触发componentWillReceiveProps这个钩子,然后依次执行子组件中的下方的钩子函数

新的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCSBO2na-1632465085234)(D:\LearningResource\前端\React教程资料和源码\react全家桶资料\02_原理图\react生命周期(新)].png)

static getDerivedStateFromProps(props,state)会在调用render方法之前进行调用,并且在初始化挂载以及更新之后都会被调用,它应该返回一个对象来更新state,如果返回null则不返回任何内容,如果返回state,则之后state的值将无法继续再更新了。

getSnapshotBeforeUpdate(prevProps,prevState)在页面更新之前进行调用,它会返回一些信息,然后传递给componentDidUpdate(prevProps, prevState, snapshot)

六、父子组件通信

父传子

  1. 父组件直接将自身的数据传递到子组件中的标签内部,然后子组件通过props来接收父组件传过来的数据

子传父

  1. 子传父时,需要父组件通过props传递给子组件一个回调函数,子若想传数据,需要调用这个函数,将数据传入到参数中

如果想要在标签内部执行的方法中传入参数时,需要在执行函数的内部返回一个新的函数,或者在标签内部写成回调函数的形式,不然就会在浏览器一加载就会执行标签内部的方法!!!

消息订阅与发布

  1. 首先下载工具库、

    npm i pubsub-js --save
    
  2. 使用

    import PubSub from 'pubsub-js'
    PubSub.publish('delete',data)// 发布消息
    PubSub.subscribe('delete',function(data){}) //订阅消息
    

七、React前端配置代理

  1. 前端可以在package.json中配置

    "proxy":"http://localhost:5000"
    

    对应的地址可以换成具体要请求的服务器

    然后在发送ajax时,需要将路径换成当前脚手架启动的那个服务器路径;在请求资源时,会先在自身服务器下查找,如果有,则返回自身服务器下的资源,如果没有,则会去代理服务器那边去请求。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-53SZfi7m-1632465085236)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210920102810958.png)]

  2. 首先:创建代理配置文件

    在src下创建配置文件:src/setupProxy.js
    

    编写setupProxy.js配置具体代理规则:

    const proxy = require('http-proxy-middleware')
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
          target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值
          /*
          	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:5001',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    

    在真正发送请求时,仍然需要向本地的服务器请求数据,如果要请求代理,需要在发送ajax的路径上端口号后面加上代理 /api1只有加上后,才会去请求代理服务器中的数据。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Y4c8Cv8-1632465085237)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210920103956352.png)]

八、react的路由

使用React的路由时,首先需要下载react-router-dom

npm i react-router-dom

然后需要在页面中引入,在使用react路由时,需要在外层包裹一个路由对象BrowserRouter或者是HashRouter,与Vue的router-view相类似;通常都会把这个外层路由对象直接包裹在index中App组件的外侧

而路由的Link标签与Vue的router-link类似,象征着跳转路由的导航,在该标签中可以配置to这个属性指定跳转地址

路由的Route标签是直接映射路由的内容,可以在该标签内部配置path 和 component属性,path用来匹配对应的路由地址,component用来匹配对应的路由组件。

路由组件与一般组件

		1.写法不同:					
			一般组件:<Demo/>				
			路由组件:<Route path="/demo" component={Demo}/>		
		2.存放位置不同:					
			一般组件:components					
			路由组件:pages		
		3.接收到的props不同:					
			一般组件:写组件标签时传递了什么,就能收到什么					
			路由组件:接收到三个固定的属性										
		history:													
			go: ƒ go(n)													
			goBack: ƒ goBack()													
			goForward: ƒ goForward()													
			push: ƒ push(path, state)													
			replace: ƒ replace(path, state)										
		location:													
			pathname: "/about"													
			search: ""													
			state: undefined										
		match:													
			params: {}													
			path: "/about"													
			url: "/about"

NavLink相对于Link标签来说,会使导航具有高亮效果,当然,也可以指定activeClassName样式名

标签体内容是一个特殊的标签属性,通过this.props.children可以抽取标签体内容。

可以使用Switch标签把路由全部包起来,这样可以提高效率!

解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter

路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由			
2.具体编码:					
<Switch>						
	<Route path="/about" component={About}/>						
	<Route path="/home" component={Home}/>						
	<Redirect to="/about"/>					
</Switch>

嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数

	1.params参数						
		路由链接(携带参数)<Link to='/demo/test/tom/18'}>详情</Link>						
		注册路由(声明接收)<Route path="/demo/test/:name/:age" component={Test}/>						
		接收参数:this.props.match.params			
	2.search参数(ajax的query参数)						
		路由链接(携带参数)<Link to='/demo/test?name=tom&age=18'}>详情</Link>						
		注册路由(无需声明,正常注册即可)<Route path="/demo/test" component={Test}/>						
		接收参数:this.props.location.search						
		备注:获取到的search是urlencoded编码字符串,需要借助querystring解析			
	3.state参数						
		路由链接(携带参数)<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>						
		注册路由(无需声明,正常注册即可)<Route path="/demo/test" component={Test}/>						
		接收参数:this.props.location.state						
		备注:刷新也可以保留住参数						
	以上测试都是在BrowserRouter进行,不在hashRouter中进行!!!												
	params和search参数都会在地址栏显示,state参数不会在地址栏中显示

编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退						
	-this.prosp.history.push()						
	-this.prosp.history.replace()						
	-this.prosp.history.goBack()						
	-this.prosp.history.goForward()						
	-this.prosp.history.go()										
如果一般组件也想使用路由的一些跳转方法,则需要通过从 react-router-dom 中引入 
withRouter,withRouter是一个函数,需要将导出的组件传入withRouter的函数中。例如:withRouter(Demo) Demo是类组件的名字

BrowserRouter与HashRouter的区别

1.底层原理不一样:					
	BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。					
	HashRouter使用的是URL的哈希值。		
2.path表现形式不一样					
	BrowserRouter的路径中没有#,例如:localhost:3000/demo/test					
	HashRouter的路径包含#,例如:localhost:3000/#/demo/test		
3.刷新后对路由state参数的影响					
	(1).BrowserRouter没有任何影响,因为state保存在history对象中。					
	(2).HashRouter刷新后会导致路由state参数的丢失!!!		
4.备注:HashRouter可以用于解决一些路径错误相关的问题。

九、redux

在这里插入图片描述

图解:
components--->action对象-->store-->reducers-->store--->components
首先,Reducers会先初始化状态
然后,组件通过dispatch将action对象交给store,store把数据原来的状态previousState和action对象交给reducers,reducers将旧的数据加工完返回给store一个新的状态数据,然后store通过getState将最新的状态交给组件

精简版
redux只负责管理状态,不负责更新页面
我们会通过store.getState()去读取redux中的状态
我们会通过store.dispatch()去触发reducer中的函数取加工数据

我们一般会在挂载的生命周期钩子中去负责更新页面,例如:
	// 在生命周期钩子中检测redux的状态的改变
  componentDidMount(){
    // 一旦数据改变就会重新调用一次render
    store.subscribe(()=>{
      // 使用setState来对render进行调用
      this.setState({})
    })
  }
  
但是上面的这种方式比较繁琐,需要在每个组件都需要去重新渲染,比较麻烦,以下这种方式可以一劳永逸
我们在index.js中引入store
然后在这调用这个subscribe方法,就可以达到重新渲染页面的目的(注意:不是将原来的的ReactDOM.render函数剪切到这,而是复制一份进来!!!)

store.subscribe(() => {
  ReactDOM.render(<App />, document.getElementById('root'))
})



  1. action为动作对象,type为动作的类型,data为动作对象操作的数据,type值为字符串,唯一的

    • 例如:{type:'ADD_STUDENT',data:{name:'tom',age:18}}
    • action可以分成两种
      • {}一般对象形式的action为同步action
      • 如果action的值为一个函数,function函数形式的action为异步action,异步action中一般会调用同步action
  2. dispatch是一个函数,寓意着分发的意思,通过dispatch,把动作对象交给了store

  3. store

    store默认只接收一般对象的同步action,如果action的值是一个函数为一个异步action,那么就需要用到redux-thunk这个中间件来接收异步action了

    • 通过getState()得到state
    • 通过dispatch(action)分发action,触发reducer调用,产生新的state
    • 通过subscribe(()=>{})订阅redux中状态的更改,当产生新的state时,自动调用
  4. reducers是负责加工状态,同时,reducers也可以初始化状态,第一次reducers也初始化状态了,然后从store传过来的previousState为undefined。Reducer的本质是一个函数,在reducer中只管最基本的动作,是加还是减;rudux中的reducer必须是一个纯函数

    • 初始化时,data的值可以不传;
    • 加工时,根据旧的state和action生成新的state和纯函数
  5. actions里边也都是暴露的函数

Redux完整版

目录结构

在这里插入图片描述

  1. constant,里边包含了action对象中的type的常量模块 定义常量

在这里插入图片描述

  1. actions,里边包含了加工数据的动作对象 生成操作对象

在这里插入图片描述

  1. reducer,里边包含了加工数据的具体方法 具体对数据进行CRUD

在这里插入图片描述

  1. store,里边是redux的核心对象 创建store对象
    在这里插入图片描述

如何使用接收异步action的中间件

npm i redux-thunk

在store.js下从redux-thunk引入thunk
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'

从redux中引入可执行中间件这个函数
import {createStore,applyMiddleware} from 'redux'

最后在创建store时,applyMiddleware作为第二个参数传入,然后将thunk传入
export default createStore(countReducer,applyMiddleware(thunk))

十、react-redux

在这里插入图片描述

容器组件与UI组件
UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
容器组件:负责和redux通信,将结果交给UI组件。
如何创建一个容器组件靠react-redux 的 connect函数

  • connect(mapStateToProps,mapDispatchToProps)(UI组件)
  • mapStateToProps:映射状态,返回值是一个对象
  • mapDispatchToProps:映射操作状态的方法,返回值是一个对象

1.容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

2.mapDispatchToProps,也可以是一个对象,为对象时,直接写操作数据的action即可

精简写法

  1. 容器组件和UI组件整合一个文件

  2. 无需自己给容器组件传递store,给包裹一个即可。

  3. 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

  4. mapDispatchToProps也可以简单的写成一个对象

  5. 一个组件要和redux“打交道”要经过哪几步?

    • 定义好UI组件—不暴露

    • 引入connect生成一个容器组件,并暴露,写法如下:

      connect(
        state => ({key:value}), //映射状态
        {key:xxxxxAction} //映射操作状态的方法
      )(UI组件)
      
  6. 在UI组件中通过this.props.xxxxxxx读取和操作状态

在redux中,使用push\pop等方法时,为什么原来的数组不会发生改变,而使用扩展运算符就会生效?

实际上原来的数组也发生了变化,只不过页面没有发生跟新操作,因为redux在底层做了一个判断,如果返回的新数组和之前的数据所在的内存地址是一样的,redux就不会进行页面的更新,

开发者工具的使用

  1. yarn add redux-devtools-extension或者npm i redux-devtools-extension

  2. .store中进行配置

    import {composeWithDevTools} from 'redux-devtools-extension'
    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

从0开始如何搭建react-redux,从而共享数据

  1. 首先应该在index.js入口文件下引入Provider import { Provider } from 'react-redux'

  2. 然后使用该组件将APP组件进行包裹,目的是让APP中的组件都可以共享store

    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    )
    
  3. 在src目录下创建redux文件夹

    1. 创建store核心文件store.js,该文件主要用于创建store核心仓库

      // createStore() 用于创建redux中最为核心的store对象 
      // 引入applyMiddleware中间件,用于进行异步action的操作
      import {createStore,applyMiddleware} from 'redux'
      
      // 引入总的reducers
      import allReducers from './reducers'
      
      // 引入redux-thunk,用于支持异步action
      import thunk from 'redux-thunk'
      
      // 创建store并导出
      export default createStore(allReducers,applyMiddleware(thunk))
      
    2. 创建constant.js常量模块,这里用与创建多个action操作对象中的type常量

    3. 创建reduces文件夹,里边每一个js文件模块为对应的reducer所服务的模块

      /* 
        该文件是为了创建一个为count组件服务的reducer,reducer的本质是一个函数
        reducer会接受到两个参数
          preState 之前的状态
          action   动作类型对象
      */
      
      import { INCREMENT, DECREMENT } from "../constant"
      
      // 首先初始化状态
      const initState = 0
      export default function countReducer(preState = initState, action) {
      
        const { type, data } = action
        // 根据type判断如何加工数据
        switch (type) {
          case INCREMENT:
            return preState + data
      
          case DECREMENT:
            return preState - data
          default:
            // 这块是个初始化的状态
            return preState
        }
      }
      
    4. 创建actions的文件夹,这里边的每个js文件包含了每个模块对应的action动作对象

      /* 
        该文件专门为count组件生成action对象
      */
      
      import {INCREMENT,DECREMENT} from '../constant'
      
      // 同步action
      export function createIncrementAction(data) {
        return { type: INCREMENT, data }
      }
      export function createDecrementAction(data) {
        return { type: DECREMENT, data }
      }
      
      // 异步action  就是指action的值为函数
      export function createIncrementAsyncAction(data,time) {
        return (dispatch)=>{
          setTimeout(()=>{
            // 通知redux+data
            dispatch(createIncrementAction(data))
          },time)
        }
      }
      
    5. 在每一个容器组件如果想要共享redux中的数据,需要在组件引入connect这个函数,而且需要将其暴露并传入一个UI组件,connect函数会接收到两个参数,第一个函数的返回值为redux总的状态对象,第二个函数的返回值为触发操作对象的方法,组件如果要调用对应的方法,则需要通过this.props.xx的方式去触发

      import { connect } from 'react-redux'
      
      export default connect(
        state=>({person:state.personArr}),
        {
          addPerson:createAddPerson
        }
      )(index)
      

十一、React的扩展知识

1. setState

setState更新状态的2种写法
	(1). setState(stateChange, [callback])------对象式的setState
            1.stateChange为状态改变对象(该对象可以体现出状态的更改)
            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
					
	(2). setState(updater, [callback])------函数式的setState
            1.updater为返回stateChange对象的函数。
            2.updater可以接收到state和props。
            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
		1.对象式的setState是函数式的setState的简写方式(语法糖)
		2.使用原则:
				(1).如果新状态不依赖于原状态 ===> 使用对象方式
				(2).如果新状态依赖于原状态 ===> 使用函数方式
				(3).如果需要在setState()执行后获取最新的状态数据, 
					要在第二个callback函数中读取
				
		3.对象式的setState需要获取原来的状态值,函数式的setState不需要获取,可以直接修改

2. lazyLoad

路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))

// fallback回调的loading组件直接引入,不可以使用懒加载
// 需要懒加载的路由必须用<Suspence></Suspence>组件进行包裹
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
                    <Switch>
                      <Route path="/xxx" component={Xxxx}/>
                      <Redirect to="/login"/>
    								</Switch>
</Suspense>

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(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行,不会监测任何状态,如果不传入第															二个参数,默认监测所有状态,传入谁,监测谁
    
(4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
    	componentWillUnmount() 
5. Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = React.useRef()
					// 取值:refContainer.current.value
(3). 作用:保存标签对象,功能与React.createRef()一样

4. Fragment

使用

去除掉根标签div

// 这个标签只能够拥有一个key属性,其他属性不能传入
<Fragment><Fragment>

// 也可以将组件的根标签写成空标签,但是空标签不可以写任何属性
<></>
作用

可以不用必须有一个真实的DOM根标签了

5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用
1) 创建Context容器对象:	
	const XxxContext = React.createContext()  	

2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
  <xxxContext.Provider value={数据}>		
    子组件   
  </xxxContext.Provider>   

3) 后代组件读取数据:	
  //第一种方式:仅适用于类组件 	 
  static contextType = xxxContext  
  // 声明接收context	 
  this.context // 读取context中的value数据	  	
  
	//第二种方式: 函数组件与类组件都可以	 
  <xxxContext.Consumer>	   
    {	      
      // value就是context中的value数据	        
      value => (
    		要显示的内容
    	)	    
  	}	  
  </xxxContext.Consumer>
注意
在应用开发中一般不用context, 一般都用它的封装react插件

6. 组件优化

Component的2个问题
  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决
办法1: 	
	重写shouldComponentUpdate()方法	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
	从react中导入PureComponent,让组件去继承,而不是直接继承Component
  使用PureComponent	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true	
  注意: 		
    使用PureComponent只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false;
    不要直接修改state数据, 而是要产生新数据
    项目中一般使用PureComponent来优化

7. render props

如何向组件内部动态传入带内容的结构(标签)?
Vue中: 	
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:	
	使用children props: 通过组件标签体传入结构	
		使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
		其实render属性可以写成任意名字,只不过在调用的时候需要写的方法名一致才行!
children props
<A>  
	<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)} 这个等同于Vue的插槽技术,相当于挖一个坑,让其他数据来填补
C组件: 读取A组件传入的数据显示 {this.props.data} 

可以在父组件中使用render这个属性来渲染子组件,render内部可以传入一个回调函数,在回调函数中可以把要传递的数据带过去,然后可以供子组件去使用

8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

state = {
  hasError:'', // 用于标识子组件是否出现c
}
// 生命周期函数,一旦后台组件报错,就会触发
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(开发用的少,封装插件用的多)

十二、纯函数和高阶函数

1、什么是纯函数?

  1. 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 必须遵守以下条件
    • 不改写参数数据
    • 不发送网络请求,以及输入和输出
    • 不调用Date.now()或者Math.random()

2.什么是高阶函数?

  1. 参数是一个函数
  2. 返回值是一个函数

常见的高阶函数

  1. 定时器设置函数
  2. 数组的forEach\map\filter\reduce\find\bind
  3. promise
  4. react-redux中的connect函数
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值