React

React基础入门

React背景

在这里插入图片描述

React特点

在这里插入图片描述

react相关库
react.development.js (核心库)
react-dom.development.js (引入react-dom用于支持操作dom)
babel.min.js (用于将ES6转为ES5,将react中jsx语法转为js,jsx中标签转为html中标签)
引入具有 先后顺序,核心库必须先引入;
使用jsx语法的注意事项
1、jsx语法必须包裹在<script type="text/babel"></script>标签中;
2、body中必须存在一个标签用于将虚拟dom转为为真实dom渲染到其中去的容器;
3、jsx标签不能用双引号包起来;
4、jsx中如果要使用js代码则需要用{}包起来;
5、如果要添加类需要使用className="类名";
6、如果要添加行内样式需要style={{}},外层{}表示其中是js代码,内层{}是一个对象通过key-value来添加样式的;
7、在jsx中以小写开头的标签会被解析成js中的html标签

        <goods>123123</goods>
        react-dom.development.js:500 Warning: The tag <goods> 
        is unrecognized in this browser. If you meant to render 
        a React component, start its name with an uppercase letter.
8、在jsx中以大写开头的标签会被默认解析成一个组件,没有定义此组件的话则报未定义

      <Goods></Goods>
      Inline Babel script:16 Uncaught ReferenceError: Goods is not defined

9、创建的虚拟DOM只能存在一个根标签
10、所有标签必须闭合

在这里插入图片描述

react创建虚拟DOM的两种方式
第一种:使用jsx语法
需要引入babel.js及script的type为"text/babel";
1、创建虚拟DOM;
const VDOM = <h2>我是jsx语法</h2>
2、渲染到页面上;
ReactDOM.render(VDOM,document.getElementById("test"))
——————————————————————————————————————————————————————————
第二种:利用js语法
利用js语法时就不需要引入babel.js及script的type也不用改变
1、创建虚拟DOM;
const VDOM = React.creatElement('h2',{},"我是js语法")
2、渲染到页面上;
ReactDOM.render(VDOM,document.getElementById("test"))

jsx的在此处的优点,可以更便捷的进行标签嵌套操作;

React定义组件

定义组件具有两种方式函数式组件(简单组件)和类式组件(复杂组件);
区分简单组件与复杂组件的关键在于有无state(状态)
常用的是类式组件;

函数式定义组件
function MyComponent(){
	console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
	return <h2>函数式定义组件</h2>
}
ReactDOM.render(<MyComponent/>,document.getElementById("test"))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
类式组件
class MyComponent extends React.Component { //继承自react内置的类
	render(){
		console.log(this)//--->this是组件实例对象MyComponent
		return (
			<h2>类式定义组件</h2>
		)
	}
}
ReactDOM.render(<MyComponent/>,document.getElementById("test"))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1、React解析组件标签,找到了该组件标签;
2、发现组件是由类定义的,react随后new出来该类的实例,并由组件实例对象调用原型上的render方法
3、将render返回的虚拟DOM转为真实DOM渲染到页面中去;
*/

组件实例的三大属性之state(状态)

复习类中方法this的指向
class Person{
        constructor(name,age){
            this.name = name
            this.age = age
        }
        speak(){
            console.log(this);
        }
    }
    let p1 = new Person("tom",18)
    p1.speak() //构造的实例对象调用 Person
    let p2 = p1.speak
    p2() //直接调用 undefined


    // 类中方法会默认开启局部的严格模式,禁止方法中this指向window
    function study(){
        "use strict"
        console.log(this);
    }
    study()

    **总结**只有由实例对象调用的方法中的this才为这个实例对象
    而直接调用或作为回调时此时的this将为window,
    但是在类中会默认开启局部的严格模式,禁止方法中this指向window

组件称为“状态机”,通过更新组件的state状态来更新对应页面的显示即重新渲染组件

更换天气案例
复杂写法
<script type="text/babel">
  class Weather extends React.Component {
    // 修改实例对象的状态
    //ES6 的状态简写写法
    constructor(props) {
      super(props);
      this.state = { isHot: true, wind: "微风" };
      this.changeWeather = this.changeWeather.bind(this); 
      //通过bind来修改实例对象中的this指向
    }
    render() {
      const { isHot, wind } = this.state; //解构赋值
      return (
        //jsx中绑定事件必须以驼峰命名
        //这里this.changeWeather是作为onClick的回调来执行的,而不是由构造的实例对象调用的,所以其this是指向window的,
        //但是类中所有定义的方法会开启局部的严格模式(在babel下也是)双重限制,禁止this指向全局的windows
        <h2 onClick={this.changeWeather}> 
          今天天气很{isHot ? "炎热" : "凉爽"},{wind}
        </h2>
      );
    }
    changeWeather() {
      console.log(this); //类中所有定义的方法会开启局部的严格模式(在babel下也是),禁止this指向全局的windows
      let isHot = this.state.isHot;
      this.setState({ isHot: !isHot }); //修改state中的状态必须使用setState方法
    }
  }

  ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
简洁写法
<script type="text/babel">
    class Weather extends React.Component {
    //ES7 的状态简写写法
        state={
            isHot:false,
            wind:"微风"
        }
        render() {
            const{isHot,wind} = this.state
            return <h2 onClick={this.change}>今天天气是{isHot ? "炎热" : "凉爽"},{wind}</h2>
        }
        change=()=>{ //这种写法仅仅只是一种提按,
            let isHot = this.state.isHot
            this.setState({isHot:!isHot})
        }
    }
    ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>

组件实例的三大属性之props(属性)

props的基本使用

在这里插入图片描述

关于批量传递属性到props
    let person = {name:'tom',sex:"男",age:"18"}
    // 这里的模拟从后台请求来的数据
    ReactDOM.render(<Person {...person}/>,document.getElementById("test1"))
    // {...person} 实质上是js中包含了扩展运算符...person,是上面那种的语法糖,组件从
    // 外部携带过去的数据会被存放在props中

但是我们考虑一个需求,如果传递的属性时需要限制其类型或者,不传此属性时会有一个默认值该如何去处理;
这是就需要标志:
在这里插入图片描述
在这里插入图片描述
Person.propTypes ->标志着需要传递属性类型进行限制;
Person.defaultProps ->标志这传递属性时的默认值;

  // 传递属性时对类型的限制及是否必须传递
  Person.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    sex: PropTypes.string,
    speak: PropTypes.func,
  };
  // 向组件内部传递属性时的默认值
  Person.defaultProps = {
    sex: "女",
    age: 19,
  };

给类添加属性

  // 给类添加属性
  class Car {
    // 第二种
    static brand = "保时捷";
  }
  // 第一种
  Car.dome = 100;
  console.log(Car.dome);
  console.log(Car.brand);
    // 传递属性时对类型的限制及是否必须传递
    static propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number,
      sex: PropTypes.string,
      speak: PropTypes.func,
    };
    // 向组件内部传递属性时的默认值
    static defaultProps = {
      sex: "女",
      age: 19,
    };

// this.props.age = 18 //因为props在组件内部是只读的,不可以修改
//会报错 Uncaught TypeError: Cannot assign to read only property ‘age’ of object ‘#’

在这里插入图片描述

函数式组件中的props
  function Person(props) {
    // 函数接收一个props参数,其为一个对象,保存了组件从外部传递进来的属性
    const { name, age, sex } = props;
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>年龄:{age + 1}</li>
        <li>性别:{sex}</li>
      </ul>
    );
  }

  // 告知react底层需要添加类型限制,以及其必要性
  Person.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    sex: PropTypes.string,
  };
  //   告知react底层给属性添加默认值
  Person.defaultProps = {
    age: 19,
    sex: "男",
  };

  let p1 = { name: "tom", age: 18, sex: "男" };
  ReactDOM.render(<Person {...p1} />, document.getElementById("test1"));

组件实例的三大属性之refs

refs的作用,用于绑定事件后对值的操作,保存绑定事件的jsx标签转化后的真实DOM;

字符串形式的ref
    class Dome extends React.Component {        
        render(){
            return (
                <div>
                    {/*2、其中的ref给当前jsx标签添加了一个标识,而组件实例对象中的refs中保存
                    的就是加了标识的当前jsx标签转化成的真实DOM标签*/}
                    <input ref="input1" type="text" placeholder="点击按钮弹出内容"/>&nbsp;
                    <button onClick={this.showData1}>点击显示第一个内容</button>&nbsp;
                    <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点弹出内容"/>
                </div>
            )
        }
        // 1、refs属性中保存的是真实DOM
        showData1=()=>{
            console.log(this.refs);
            const {input1} = this.refs
            alert(input1.value)
        }
        showData2=()=>{
            const {input2} = this.refs
            alert(input2.value)
        }
    }
    ReactDOM.render(<Dome/>,document.getElementById("test"))
回调形式的ref

当将回调写在行内时,会出现一个问题,就是ref的函数会被调用两次,例如更改state中的状态,因为更改state中的状态会重新渲染组件,会重新调用组件实例对象的render函数,此时react不知道ref上次调用所保存的DOM元素是否被清空了,所以会先调用一次,将当前ref变为null,然后再调用一次对其DOM元素进行保存;

class Dome extends React.Component {
    state = { isHot: true };
    render() {
      return (
        <div>
          {/*当new出来组件实例对象时,组件实例对象调用原型的render函数发现其中jsx语法中具有ref标识的函数
                        然后组件实例对象就会调用这个函数,这个函数会传入一个参数,这个参数就是当前这个转为的真实DOM
                    */}
          <h2>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h2>
          <button onClick={this.changeWeather}>点击切换天气</button>&nbsp;
          <input
            ref={(element) => {
              (this.input1 = element), console.log("@",element);
            }}
            type="text"
            placeholder="点击按钮弹出内容"
          />
          &nbsp;
          <button onClick={this.showData1}>点击显示第一个内容</button>&nbsp;
          <input
            ref={(current) => {
              this.input2 = current;
            }}
            onBlur={this.showData2}
            type="text"
            placeholder="失去焦点弹出内容"
          />
        </div>
      );
    }
    showData1 = () => {
      console.log(this);
      const { input1 } = this;
      alert(input1.value);
    };
    showData2 = () => {
      const { input2 } = this;
      alert(input2.value);
    };
    changeWeather = () => {
      const { isHot } = this.state;
      this.setState({ isHot: !isHot });
    };
  }
  ReactDOM.render(<Dome />, document.getElementById("test"));

但是将回调函数写在组件实例对象身上,再赋值过去作为ref的回调,这时将会解决调用两次这个问题。但事实上这个问题不影响程序的运行。

creatRef形式的ref

React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的;

    class Dome extends React.Component {    
        // 通过react内置的方法在组件实例对象身上创建一个容器
        // 但是一个容器只能保存一个ref
        input1 = React.createRef()
        input2 = React.createRef()
        render(){
            return (
                <div>
                    <input ref={this.input1} type="text" placeholder="点击按钮弹出内容"/>&nbsp;
                    <button onClick={this.showData1}>点击显示第一个内容</button>&nbsp;
                    <input ref={this.input2} onBlur={this.showData2} type="text" placeholder="失去焦点弹出内容"/>
                </div>
            )
        }
        showData1=()=>{
            alert(this.input1.current.value)
        }
        showData2=()=>{
            alert(this.input2.current.value)
        }
    }
    ReactDOM.render(<Dome/>,document.getElementById("test"))

事件处理

(1).通过onXxx属性指定事件处理函数(注意大小写)
	a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件,其在内部将原生DOM事件都重新封装了一遍;
	—————— 为了更好的兼容性
	b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
	————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象(发生事件的事件源)
	——————————不要过度使用ref
	a.当发生事件的元素和你要操作的dom元素是一个的话就可以不使用ref,
	而直接使用event.target,来获取事件源

组件分类

非受控组件

操作dom元素的值是通过ref属性保存,然后直接被读取出来的,不是动态的。

  class Login extends React.Component {
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交默认行为
      const {username,password} = this
      alert(`你的账号是${username.value},你的密码是${password.value}`)
    };
    render() {
      return (
        <form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
          账号:
          <input ref={c => this.username = c}  type="text" name="username" />
          <br />
          密码:
          <input ref={c => this.password = c} type="password" name="password" />
          <br />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));
受控组件

受控组件是会将操作的元素的值保存在state状态中,再通过onChange就实现了动态绑定,类似于vue中的双向绑定,而且还可以避免使用ref

  class Login extends React.Component {
    state = {
      username: "",
      password: "",
    };
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交默认行为
      const {username,password} = this.state
      alert(`您的账号是${username},您的密码是${password}`)
    };
    username = (event) => {
      this.setState({ username: event.target.value });
    };
    password = (event) => {
      this.setState({ password: event.target.value });
    };
    render() {
      return (
        <form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
          账号:
          <input onChange={this.username} type="text" name="username" />
          <br />
          密码:
          <input onChange={this.password} type="password" name="password" />
          <br />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));
使用高阶函数和函数柯里化提升性能
什么是高阶函数?
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
	1.A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
	2.A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
什么是函数柯里化?
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后
统一处理的函数编码形式。 
			function sum(a){
				return	(b)=>{
					return (c)=>{
						return a+b+c
					}
				}
			}
运用函数柯里化来提升性能
class Login extends React.Component {
    state = {
      username: "",
      password: "",
    };
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交默认行为
      const {username,password} = this.state
      alert(`您的账号是${username},您的密码是${password}`)
    };
    // 定义一个方法供获取input值使用
    saveFormData=(dataType)=>{
        return (event)=>{
            // 这里key默认是字符串,要想使用使用变量则需要加上[]
            // 函数调用继续返回函数然后再调用,最后进行统一处理(函数柯里化)
            this.setState({[dataType]:event.target.value})
        }
    }
    render() {
      return (
        <form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
          账号:
          {/*这里将this.saveFormData()的返回值(返回值仍为一个函数)作为onChange方法的回调
            当input中value发生改变时调用此函数
        */}
          <input onChange={this.saveFormData("username")} type="text" name="username" />
          <br />
          密码:
          <input onChange={this.saveFormData("password")} type="password" name="password" />
          <br />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));

生命周期(旧)

在这里插入图片描述
一、挂载时很好理解,按照生命周期向下走就行;
二、更新时有三种状态:
1、setState()改变状态时;
2、forceUpdate强制刷新,但是需要自己去调用;
3、父组件render,就是在父组件渲染时向子组件中的props传入数据,然后当传入子组件数据是新数据时,便可回调componentWillReceiveProps()钩子;
注意:其中componentWillReceiveProps()钩子中可以传入props参数,用来接收父组件中所传入的props;控制组件是否更新还有一个“阀门”,那就是shouldComponentUpdate();

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
		1.	constructor()
		2.	componentWillMount()
		3.	render()
		4.	componentDidMount() =====> 常用
			一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
		0.	componentWillReceiveProps()
		1.	shouldComponentUpdate()
		2.	componentWillUpdate()
		3.	render() =====> 必须使用的一个
		4.	componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
		1.	componentWillUnmount()  =====> 常用
			一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

生命周期(新)

新的生命周期中不推荐使用三个函数:
componentWillMount
componentWillReceiveProps
componentWillUpdate
如果要使用它在现版本中需添加UNSAFE_前缀,因为在未来版本中这三个钩子函数将会被删除;
在这里插入图片描述
但是新添加了两个不常用的生命周期函数
getDerivedStateFromProps()

// 从props中获取一个派生的state
// 接收的props为外部传入的props,接收的state为初始化state
 static getDerivedStateFromProps(props, state) {
   console.log("getDerivedStateFromProps", props, state);
   return null;
// 返回这个props可以将其返回的对象加入到state中去
// 使用场景,当state的值始终由外部传入props决定时使用此函数,
// 一般不用,因为派生状态会导致代码冗余,并使组件难以维护。

getSnapshotBeforeUpdate()

用于保存快照值,返回的是null或snapshot_value,最后可以
在componentDidUpdate中使用
=1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
	=1.	constructor()
	=2.	getDerivedStateFromProps 
	=3.	render()
	=4.	componentDidMount() =====> 常用
	=一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
	1.	getDerivedStateFromProps
	2.	shouldComponentUpdate()
	3.	render()
	4.	getSnapshotBeforeUpdate
	5.	componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
	1.	componentWillUnmount()  =====> 常用
	般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

Diff算法和虚拟DOM中key

经典面试题:
 1). react/vue中的key有什么作用?(key的内部原理是什么?)
 2). 为什么遍历列表时,key最好不要用index?
      
	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脚手架

简述脚手架

xxx脚手架诞生:用于快速搭建一个基于xxx库的模板项目;
(vue | vue/cli;react | create-react-app)
——1、包含了所有需要的配置(语法检查,jsx编译,DevServer…)
——2、下载了所有相关依赖;
——3、可以直接运行一个简单的效果;
react提供了一个用于创建react项目的脚手架库:create-react-app;
项目的整体技术架构为: react + webpack + es6 + eslint;
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化;

创建项目并启动

第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
在这里插入图片描述

脚手架项目结构

初始化文件夹的时候还帮忙建立了一个本地的git仓库;
public文件夹
在这里插入图片描述
如果没有使用应用加壳技术和爬虫的话,就可以只保留前两个文件即可;
————————————————————————————————
src文件夹
在这里插入图片描述
但是直接通过create-react-app xxxx创建出来的组件化,模块化,工程化项目的目录结构是不符合目前我们的开发需求的,我们可以将多余的文件删除;
在这里插入图片描述

样式模块化

为什么需要样式模块化,因为react中样式都是以className形式引入的类式样式,所以当多个组件被包裹到外壳组件App中时可能会出现类重名,从而导致样式覆盖,所以才有了样式模块化;
在这里插入图片描述
需要这样来接收样式,但是如果我们使用less来进行嵌套样式那么将不会出现这个问题,因为最外层会有此组件的一个标识Hello{.title{ } };

组件化编码流程

在这里插入图片描述

组件间通信

绑定键盘事件

在这里插入图片描述

父传子是使用props

可以单独传递
在这里插入图片描述
也可以批量传递
在这里插入图片描述

子传父

第一步:首先还是使用props传递给子组件一个可传参数的方法;
在这里插入图片描述
第二步:在子组件中调用时将需要递到父组件的数据作为参数传递到方法中并调用方法;
在这里插入图片描述

组件通信案例

在这里插入图片描述
在这里插入图片描述

总结

在这里插入图片描述

React_ajax

在这里插入图片描述
跨域是请求成功发送了,在返回数据时被ajax引擎拦截了;
产生跨域本质是ajax引擎将返回的响应给拦截了,但是中间件(代理)是通过请求转发的形式,其没有ajax引擎,同源策略(cors)不限制它;
在这里插入图片描述

脚手架配置代理(配置一个代理,优先匹配前端资源)

在脚手架的package.json中配置一个:
在这里插入图片描述
然后再将发送请求的端口号改为本地端口,这样就配置好了一个中间件(代理)
————
我们的public文件夹其实就是脚手架帮我们开启的微型服务器devServer的根路径;
在这里插入图片描述
当请求的地址在public中存在时,会直接返回public文件夹中的数据不会再转发,如果不存在时才会转发,向服务器发送请求;
在这里插入图片描述

脚手架配置代理(需配置多个代理时)

1、首先在src目录下创建一个setupProxy.js文件,文件名固定因为在react底层遍历文件时,发现存在这个文件就会将这个文件与webpack的配置文件合并;
2、然后在文件中导入proxy中间件,因为这里的代码要与webpack中配置合并所以需要使用commonjs的语法,而不能使用ES6的语法;

   //引入proxy代理中间件模块
   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': ''}
       })
     )
   }

3、然后再发送请求时需要在地址前加上代理标识,如果配置多个代理,请求时的端口号就为react本地服务的端口号;通过api1代理标识再决定要转发到那个服务器去;
—————————————————————————————————
|也就是站在3000端口给3000端口的中间件发请求,然后让其再转发到5000|
|站在3000给3000发请求时,可以省略http://localhost:3000 —————–|
—————————————————————————————————
在这里插入图片描述

GitHub搜索案例

连续解构赋值并重命名

在这里插入图片描述

为什么在访问github是可以不用配置代理

因为 https://api.github.com在后端用cors进行了配置,加上一个特殊的响应头,所以前端不需要配置代理;但是向这种公开性的网址 不希望你频繁去请求,所以可能会出现401error没有权限错误;

一个组件需要展示不同数据

记住状态驱动着页面的显示,
在这里插入图片描述
向父组件传递数据
在这里插入图片描述
在list中展示数据
在这里的a标签如果写了target="_blank"的话就必须添加ref="noreferrer"属性;
img标签是必须要有alt属性,就是图片不显示时展示文本;

在这里插入图片描述

消息订阅与发布(pubsub-js)

适用于任意组件间的数据传递。
首先安装pubsub-js依赖;

引入
import PubSub from "pubsub-js";

组件进行消息订阅subscribe
  // 消息订阅,在组件挂载完毕时
  componentDidMount() {
    this.token = PubSub.subscribe("github", (msg, stateObj) => {
      this.setState(stateObj)
    });
  }
  // 在组件将要卸载时取消消息订阅
  componentWillUnmount() {
    PubSub.unsubscribe(this.token);
  }
  ——————————————————————————
组件进行消息发布publish
PubSub.publish("github", { isFirst: false, isLoading: true });

消息订阅发布时,第一个参数均是消息名
subscribe订阅第二个参数是个回调函数,其有两个参数,分别是消息名和接收的publish发布过来的数据。

扩展:Fetch

xhr与fetch
xhr是XMLHttpRequest

像jquery还有axios都是讲xhr封装了一遍,以供我们更简便的使用,使用它们需要先下载相关库;

fetch

是原生函数,存在与浏览器中,但是不是所有浏览器都兼容fetch
其不再使用XmlHttpRequest对象提交ajax请求;
并且其使用了关注分离设计模式
例如:
——普通的请求,我让你去楼下超市买瓶水,然后你就把水买回来给我了;但是有可能超市没开等让其未成功买水的情况,所以有;
——fetch请求,我让你去楼下超市买瓶水,你到楼下超市给我说有水,然后你再把水买回来给我;
这就类似于关注分离设计模式;

fetch相关API
GET
fetch(url).then(function(response) {
    return response.json()
  }).then(function(data) {
    console.log(data)
  }).catch(function(e) {
    console.log(e)
  });
POST
fetch(url, {
    method: "POST",
    body: JSON.stringify(data),
  }).then(function(data) {
    console.log(data)
  }).catch(function(e) {
    console.log(e)
  })
    // 使用fetch来发送请求,关注分离设计模式
		//发送网络请求---使用fetch发送(未优化)
	fetch(`/api1/search/users2?q=${keyWord}`).then(
      response => {
				console.log('联系服务器成功了')
				return response.json()
			},
      error=>{
        console.log('联系服务器失败了',error);
        return new Promise(()=>{})
      }
      // 调用fetch().then()返回的是个Promise对象;
      // 当在其成功时返回值就会作为这个Promise对象.then()方法成功的返回值;
      // 当其失败时,我们返回一个新建的一个Promise对象,这样就不会进入当前这个Promise的then()方法
		).then(
			response => {console.log('获取数据成功了',response);},
      error=>{console.log("获取数据失败了",error);}
		)
  };

连续的callback看起来有点繁琐,这种方式是可以优化的,例如用async await ,try{}catch(){},可以更加简化fetch请求,
在这里插入图片描述
因为运用fetch请求返回的是一个Promise对象,所以能使用await,await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try…catch 捕获。
注意使用await是,其外部要包有个async函数

对SPA(single page web application)应用的理解

单页面富应用,只有一个完整的页面;
更新页面不会整体刷新,而是局部刷新;
数据都需要通过ajax请求获取,并在前端异步展现;

前端路由与后端路由

???
在这里插入图片描述

前端路由的实现

前端路由实现还是需要是靠BOM对象的history属性;
浏览器的历史记录是一个栈的结构;
当历史记录发生改变时会将检测到这个路径,将这个路径压入栈中,并返回对应的组件。
在这里插入图片描述
//方法一,直接使用H5推出的history身上的API
//方法二,hash值(锚点)

前端路由的原理:
1、点击导航链接引起路径变化;
2、路径的变化被前端路由器检测到,进行匹配组件从而展示;

react-router-dom(web人员专属)

对于react-router下是存在三个仔细划分web,native,any,我们学习的是web版的,对于web人员来说相对方便一些;
在这里插入图片描述

使用步骤

首先安装react-router-dom库
在这里插入图片描述
在这里插入图片描述
这里就是说需要在外部需要包裹有个< Router >,这个< Router >是分为两种< BrowserRouter>和< HashRouter >;

ReactDOM.render(
    // 直接将BrowserRouter包裹在App外壳组件外部
    // 因为项目只允许存在一个检测器
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
 在React中靠路由链接实现切换组件--编写路由链接
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
注册路由 
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
Hash与history中H5方式

hash值锚点值,在‘#’后面的不会发送到服务器端去,会被当成前端资源;

路由组件与一般组件

一般组件,直接< Home />
路由组件,使用< Link />编写路由链接,注册路由,如果路由路径匹配上了,就展示对应的组件,此时就叫做路由组件;

不同点

1、写法不同;
一般组件:< Header />
路由组件:< Route path="/home" component={Home}>< Route/ >
2、存放位置不同;
一般组件:components文件夹
路由组件:pages文件夹
3、接收到的props不同;
一般组件:写组件标签时传递什么就收到什么
路由组件:接收到固定的三个属性
在这里插入图片描述

NavLink

默认用于给当前选中的导航加active类,也可以自己通过其自身的activeClassName属性来指定选中时添加类名;
在这里插入图片描述

NavLink路由组件使用缺点

我们发现如果有多个导航链接代码会过于冗余,所以我们将导航封装为一个一般组件< MyNavLink />;
1、首先我们需要创建一般组件然后我们要知道路由to去哪是我们需要从外部传递进去的。然后显示的文本内容其实也是可以由props传递过去,属性名叫children;
定义MyNavLink组件:
在这里插入图片描述
使用MyNavLink组件:
在这里插入图片描述
props传递过去的数据:
在这里插入图片描述

解决多级路径刷新页面样式丢失问题;

在这里插入图片描述
在这里插入图片描述
当我们需要在每个路由前加一个统一标识的时候,我们前端引入的静态资源在刷新时可能会样式丢失;
在这里插入图片描述
实际上在使用webpack与脚手架时开启的本地服务器就是public,当请求服务器public静态资源路径出现错误时,就会返回index.html页面;

Switch的使用

1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。

路由的严格匹配与模糊匹配

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致);
2.开启严格匹配:< Route exact={true} path="/about" component={About}/>;
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由;

Redirect的使用

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:

	< Switch>
		< Route path="/about" component={About}/>
		< Route path="/home" component={Home}/>
		< Redirect to="/about"/>
	< /Switch>
嵌套路由

1.注册子路由时要写上父路由的path值;
2.路由的匹配是按照注册路由的顺序进行的。

注意!!!路由匹配时做了那些事

初次渲染时:
首先上来时初次渲染App组件,发现App中注册了一级路由并且默认(redirect)显示/home路由然后再看是否存在当前一级路由下的二级路由,如果存在就将/home路由与二级路由匹配,如果二级路由没有匹配的就显示默认(redirect)二级路由;
点击一级路由时:
将/about路由与一级路由进行匹配,然后再根据其二级路由的情况来决定是否显示二级路由;
点击二级路由时:
**路由的匹配时从一级路由开始向下匹配的,**所以当点击二级路由/home/news时,首先会和一级路由匹配,通过模糊匹配到了/home一级路由,然后渲染Home路由组件,然后发现其中也注册了路由,就是为二级路由,发现了存在/home/news二级路由,然后就渲染News路由组件;

路由传参
params传参

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

serach传参

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
利用search传递参数时 我们发现其保存在loaction属性的类型是urlencoded数据类型;
但是我们想要得到的是对象形式的数据,这样方便我们进行操作;
所以我们需要用到一个库querystring,这个库不用我们单独去安装,在安装脚手架时就已经帮我们安装好了,我们只需要去引入即可;
其内部有两个方法parse与stringify;
在这里插入图片描述
在这里插入图片描述

state传参

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

push与replace模式

默认是push模式,如果要开启路由的replace模式,在编写路由链接时就加上replace属性即可;
在这里插入图片描述

编程式路由跳转

不是通过Link,NavLink链接去跳转路由;
编程式路由跳转借助的是路由history属性身上的API;
在这里插入图片描述
在这里插入图片描述

react封装的props中的history的API

在这里插入图片描述

withRouter的作用

import {withRouter} from ‘react-router-dom’ 需要引入
在这里插入图片描述

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可以用于解决一些路径错误相关的问题。

antd-ui

## 十四、antd的按需引入+自定主题
1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
2.修改package.json
		....
			"scripts": {
				"start": "react-app-rewired start",
				"build": "react-app-rewired build",
				"test": "react-app-rewired test",
				"eject": "react-scripts eject"
			},
		....
3.根目录下创建config-overrides.js
		//配置具体的修改规则
		const { override, fixBabelImports,addLessLoader} = require('customize-cra');
		module.exports = override(
			fixBabelImports('import', {
				libraryName: 'antd',
				libraryDirectory: 'es',
				style: true,
			}),
			addLessLoader({
				lessOptions:{
					javascriptEnabled: true,
					modifyVars: { '@primary-color': 'green' },
				}
			}),
		);
	4.备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉

redux

redux是一个专门做状态管理的js库
作用是集中式管理react应用多个组件共享的状态
什么情况下需要使用redux
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

redux的基本使用

了解了redux的action createors / store / reducers三大组成后我们来了解基本使用步骤
————
1、进行redux的安装,因为redux事实上不是facebook出品的

2、在src下创建redux目录
因为action creators在简单情况下是可以不具有的
所以我们可以创建目录为
src下建立:

-redux
	-store.js
	-count_reducer.js

3、
——3.1在store中引入redux中的createStoreApi,用来创建一个store;
——3.2createStore调用时需要传入一个为其服务的reducer
——3.3将这个store暴露出去

4、count_reducer.js:
1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
2).reducer有两个作用:初始化状态,加工状态
3).reducer被第一次调用时,是store自动触发的,
传递的preState是undefined,
传递的action是:{type:’@@REDUX/INIT_a.2.b.4}

5、将store引入需要使用的组件中,可以开始使用store

 increment = () => {
    const { value } = this.selectValue
    store.dispatch({ type: 'increment', data: Number(value) })
    // 此处因为我们没有使用action creators进行创建行为对象,所以我们自己创建了一个行为对象
  }

6、此时我们redux状态改变了,但是我们的视图还没有改变,是因为redux只负责管理状态,至于状态的改变驱动着页面的展示则需要我们自己编写
利用store.subscribe(()=>{...代码块})解释,调用此API就会进行redux中状态的变化,只要变化,就调用render
1)、如果是在组件中我们一般会在componentDidMount函数中进行

  componentDidMount() {
  // 检测redux中状态的变化,只要变化,就调用render
    store.subscribe(() => {
      this.setState({}) //这个this是当前这个组件
    //  虚晃一枪改变组件中的state,但是没有改变,组件实例也会瓜兮兮的调用其原型上的render方法更新视图
    })
  }

2)、可以在index.js入口文件中进行redux检测

// 用于检测redux中状态的变化,只要有变化就调用render,
// 此时也不用过多考虑效率问题,因为具有强大的diff算法,不会进行大面积刷新
store.subscribe(() => {
  ReactDOM.render(
  <BrowserRouter>
  <App/>
  </BrowserRouter>,
  document.getElementById('root'))
})

7、这样已经可以进行redux的基本使用了,此时我们需要将aciton creators加入进来,利用其将我们需要做的事包装为一个aciton对象

aciton:{type:xxx,data:xxx}

此时我们新增两个文件;
新增文件:
1.count_action.js 专门用于创建action对象
2.constant.js 放置容易写错的type值

constant.js文件
// 保存常量,存放type值
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

count_action.js文件
import {INCREMENT,DECREMENT} from './constant'
// 构建出行为对象
export const createincrementaction = data => ({type: INCREMENT, data})
export const createdecrementaction = data => ({type: DECREMENT, data})

8、我们创建类型常量constant.js及count_action.js并暴露后我们便可以在组件进行使用

引入action_creators文件,
在dispatch时利用此文件中的方法将操作包装为行为对象交给store,
再由相应的reducers进行处理后返回给store,
最后组件可以通过store.getState()进行获取状态

import {createdecrementaction,createincrementaction} from '../../redux/count_action'

  increment = () => {
    const { value } = this.selectValue
    store.dispatch(createincrementaction(Number(value)))
  }

异步的action

求和案例_redux异步action版
(1).明确:延迟的动作不想交给组件自身,想交给action
(2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
(3).具体编码:
1).yarn/npm add redux-thunk,并配置在store中;

// 该文件专门用于暴露一个store对象,一个项目只有一个store对象

// 1.引入createStore,专门用于创建redux中最为核心的store对象
import {
  createStore,
  applyMiddleware //引入一个中间件
} from 'redux'
// 引入thunk,用于支持异步action
import thunk from 'redux-thunk'
// 2.引入为组件count组件服务的reducer
import countReducer from './count_reducer'

// 3.暴露store
export default createStore(countReducer,applyMiddleware(thunk))

2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务;
3).异步任务有结果后,分发一个同步的action去真正操作数据。
(4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

为什么一般对象就是同步action,函数类型就是异步的action?
答:因为是函数才能进行异步操作开启异步任务;而对象或常量不能开启异步任务;

react-redux

react-redux模型图
在这里插入图片描述
1、所有组件外都应该包裹一个容器组件,他们是父子关系。
2、容器组件才是真正和redux打交道的,里面可以随意的使用redux的api。
3、ui组件中不能使用任何redux的api,只负责视图的呈现事件监听
4、容器组件会传给UI组件:(1).redux中所保存的状态。(2).用于操作状态的方法。
5、备注:容器给UI传递:状态、操作状态的方法,均通过props传递。

react-redux的基本使用

react-redux是facebook出品的,其能更好的帮助我们实现状态管理

1、创建容器组件
2、将store通过props传递给容器组件
3、容器组件将状态及操作状态的方法在ui组件connect与redux建立关系时通过props传递给ui组件
// 引入ui组件
import CountUI from '../../components/Count'
// 引入action
import {
  createincrementaction,
  createdecrementaction,
  createincrementasyncaction
} from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'

// 向ui组件通过props传递state状态
function mapStateToProps(state) {
  return { count: state }
}
// 向ui组件通过props传递操作state的方法
function mapDispatchToProps(dispatch) {
  return {
    jia: number => dispatch(createincrementaction(number)),
    jian: number => dispatch(createdecrementaction(number)),
    jiaAsync: (number, timer) => dispatch(createincrementasyncaction(number, timer))
  }
}

// 使用connect()()创建并暴露一个count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
// 这里的传入mapStateToProps(映射状态), mapDispatchToProps(映射操作状态的方法)是在使用connect创建暴露时,
// 调用了这两个函数并为其传入了state与dispatch,这样就不用再引入store了,
// 因为在容器外已经向容器组件传递了store

react-redux优化

connect传参优化

我们在connect()()连接时,我们state与操作state的方法我们可以直接写在参数;

// 使用connect()()创建并暴露出一个容器组件
export default connect(
  state => ({ count: state }),
  // mapDispatchToProps的简写形式,
  // 这里我们不需要通过调用dispatch去分发,react-redux在会帮我们去调dispatch
  {
    jia: createincrementaction,
    jian: createdecrementaction,
    jiaAsync: createincrementasyncaction
  }
)(CountUI)
检测redux中状态优化

我们在先前使用componentDidMount(){}中和入口文件中使用store.subscribe(()=>{})来进行检测
利用了react-redux为我们提供了一个新的API==>Provider
1、我们将其包裹在外壳组件APP外面;
2、react-redux就可以帮我们自动检测到state状态的改变并render重新渲染视图;
3、我们将store传递给这个Provider组件,这里的Provider组件会将store自动分配给需要的容器组件中,并且会检测store中状态的改变,进而render视图。
在这里插入图片描述

合并UI组件与容器组件

将UI组件合并到容器组件中,只将容器组件暴露出去

数据共享

利用redux中combineReducers此API进行reducer合并

// 该文件专门用于暴露一个store对象,一个项目只有一个store对象
// 1.引入createStore,专门用于创建redux中最为核心的store对象
import {
  createStore,
  applyMiddleware, //引入一个中间件
  combineReducers
} from 'redux'
// 引入thunk,用于支持异步action
import thunk from 'redux-thunk'
// 2.引入为组件count组件服务的reducer
import countReducer from './reducer/count'
import personReducer from './reducer/person'

// 3、汇总后所有的reducer变为一个总的reducer
  const allReducer = combineReducers({
    he:countReducer,
    ren:personReducer
  })
// 4.暴露store
export default createStore(allReducer,applyMiddleware(thunk))

然后在使用connect()()创建容器组件传参时,便可以向一个UI组件传递多个不同状态或操作状态的方法

// 使用connect()()创建并暴露一个count的容器组件
export default connect(
  // 向ui组件通过props传递state状态
  state => ({ count: state.he,renshu:state.ren }),
  // 向ui组件通过props传递操作state的方法
  {
    jia: createincrementaction,
    jian: createdecrementaction,
    jiaAsync: createincrementasyncaction
  }
)(Count)
// 这里的传入mapStateToProps(映射状态), mapDispatchToProps(映射操作状态的方法)是在使用connect创建暴露时,
// 调用了这两个函数并为其传入了state与dispatch,这样就不用再引入store了,
// 因为在容器外已经向容器组件传递了store

就像这里我们向Count的ui组件不仅传递了he状态,还传递了renshu状态

纯函数

在这里插入图片描述
在这里插入图片描述

Hooks

react hooks是什么?
hooks是在react 16.8版本增加的新特性、新语法;
可以让我们在函数组件中使用state及其他react特性,这样就没有了无状态组件了

useState

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
做了底层处理,在第一次调用时传入initValue就将此保存下来了,不会再之后的调用进行改变
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

useEffect

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()

useRef

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
(4).最后再从标签中的current属性中获取value

react扩展

扩展一_setState

对于状态更新的方法有两种


	(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函数中读取

对于this.setState()方法是同步调用,但是更新状态是异步更新

扩展二 lazy(),路由组件懒加载

1、通过react的lazy函数配合import()函数动态加载路由组件===>这样会使路由组件分开打包;
例如:const Login = lazy(()=>import('./Login'))
2、通过<Suspense>指定在加载得到路由打包文件前显示一个自定义的Loading组件界面;
显示的这个组件不能通过懒加载的方式去引入,要保证其第一次render时就已经就位了

<Suspense fallback={<Loading></Loading>}>
   <Switch>
     <Route path="/about" component={About}></Route>
     <Route path="/home" component={Home}></Route>
   </Switch>
 </Suspense>

扩展三 <Fragment></Fragment>

作用就是可以不必具有一个真实的DOM根标签

****循环时可以将key绑定在Fragment上****
<Fragment key={1}>
	<input type="text"/>
	<input type="text"/>
</Fragment>

存在以下三种用法
在这里插入图片描述

扩展四 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 => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

在开发中我们一般不直接使用context,而是都用它的封装react插件

扩展五 组件优化

Component的2个问题

只要执行setState(),即使不改变状态数据, 组件也会重新render()
只当前组件重新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来优化

扩展六 render props

如何向组件内部动态传入带内容的结构(标签)?
Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <AA><BB/></AA>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构, 一般用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数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

扩展七 错误边界

理解:

错误边界:用来捕获后代组件错误,渲染出备用页面
会在生产环境中维持状态,在开发环境不会维持状态;

特点:

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

使用方式

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}

componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}

高阶函数与高阶组件

1. 高阶函数

1). 一类特别的函数
    a. 接受函数类型的参数
    b. 返回值是函数
2). 常见
    a. 定时器: setTimeout()/setInterval()
    b. Promise: Promise(() => {}) then(value => {}, reason => {})
    c. 数组遍历相关的方法: forEach()/filter()/map()/reduce()/find()/findIndex()
    d. 函数对象的bind()
    e. Form.create()() / getFieldDecorator()()
3). 高阶函数更新动态, 更加具有扩展性

2. 高阶组件

1). 本质就是一个函数
2). 接收一个组件(被包装组件), 返回一个新的组件(包装组件), 包装组件会向被包装组件传入特定属性
3). 作用: 扩展组件的功能
4). 高阶组件也是高阶函数: 接收一个组件函数, 返回是一个新的组件函数
组件与标签

组件是一种类型,而标签是由这个类构造的实例,组件本质上是个函数。

vue react img标签引入图片的几种方式

在这里插入图片描述

通过父组件触发事件来获取子组件中的数据

如果频繁的进行状态的更新会报错,react不允许
1、向子组件通过props传递一个函数,为父组件设置一个标识属性来接收子组件的this
在这里插入图片描述
2、如果存在这个props,则将此子组件的this传入父组件传递的函数,产生函数的回调,
在这里插入图片描述
3、此时父组件设置的标识属性即可获取子组件的所有状态,我们可以对其进行保存并使用
在这里插入图片描述
在这里插入图片描述
4、这样就可以实现点击父组件从而获取子组件的数据并进行展示了

插槽

父组件在子组件上通过props传递组件,子组件中通过props使用叫做具名插槽
父组件在子组件上通过内部内容传递内容,子组件可以通过this.props.children接收,叫默认插槽
在这里插入图片描述

Portal

Portal提供了一种将子节点渲染到存在于父组件以外的DOM节点的优秀方案;

ReactDOM.createPortal(child,container)

第一个参数是需要渲染的React子元素,第二个参数是一个Dom元素

Hooks

Hooks简介

useState

在这里插入图片描述
对象与数组的改变需要改变其内存地址才能被diff检测到

useEffect

参考
useEffect传入引用类型的依赖时,如果未改变此引用类型那么还是会触发componentDidUpdate,所以此时我们需要使用深比较的方法;等依赖内部有改变时才触发componentDidUpdate,
方法一 :将传入引用对象依赖使用JSON.stringify() 包裹成一个新的JSON字符串再传入依赖
方法二 :安装use-deep-compare-effect来替换useEffect

自定义hooks

在这里插入图片描述

优化性能

React.memo():一个高阶组件,包裹子组件返回一个新的子组件。当父组件重新渲染,就可以防止子组件的不必要渲染。但是当子组件使用到父组件的方法时,父组件重新渲染,子组件又要跟着渲染,使用useCallback,包裹父组件传递给子组件的函数,再次防止子组件渲染。 useMemo:当组件中有一个计算量非常大的一个值,当组件重新渲染时,可以防止该值再次重新计算,影响性能。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值