React 从入门到转行系列😉

429 阅读44分钟

介绍

用于动态构建用户界面的 JavaScript 库(只关注于视图)

  1. 发送请求获取数据
  2. 处理数据(过滤,整理格式等)
  3. 操作DOM呈现页面(React做的事情) React是一个将数据渲染为HTML视图的开源JavaScript库

1.2 原生JavaScript的缺点

  1. 原生JavaScript操作DOM繁琐,效率低(DOM-API操作UI)
  2. 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
  3. 原生JavaScript没有组件化编码方案,代码复用率很低

【补充】浏览器重绘重排

浏览器重绘(repaint)重排(reflow)与优化[浏览器机制]

  • 重绘(repaint):当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘
  • 重排(reflow):当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排

【补充】模块化与组件化

模块

理解:向外提供特定功能的js程序, 一般就是一个js文件 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。 作用:复用js, 简化js的编写, 提高js运行效率 组件 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等) 为什么要用组件: 一个界面的功能更复杂 作用:复用编码, 简化项目编码, 提高运行效率

  • 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
  • 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

React的特点

采用组件化模式、声明式编码,提高开发效率及组件复用率 在 React Native中可以使用React语法进行移动端开发 使用虚拟DOM+Diff算法,尽量减少与真实DOM的交互

React高效的原因

使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。 DOM Diffing算法, 最小化页面重绘。 相关库介绍 旧版本 16.8.4 (March 5, 2019) 新版本 有不一样的会说明

react.js:React核心库。 react-dom.js:提供操作DOM的React扩展库。 babel.min.js:解析JSX语法代码转为JS代码的库。

【补充】babel.js的作用 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

使用JSX创建虚拟DOM

const VDOM = <h1>Hello,React</h1>

渲染虚拟DOM(元素)

  1. 语法: ReactDOM.render(virtualDOM, containerDOM)
  2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示 参数说明
  • 参数一: 纯js或jsx创建的虚拟dom对象
  • 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello_react</title>
</head>

<body>
  <!-- 准备好一个“容器” -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>

  <script type="text/babel"> /* 此处一定要写babel */
		//1.创建虚拟DOM
		const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>

</html>

image.png

创建虚拟DOM的两种方式

3.1 纯JS方式(一般不用)

<div id="test"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>

<script type="text/javascript" > 
	//1.创建虚拟DOM
	const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
	//2.渲染虚拟DOM到页面
	ReactDOM.render(VDOM,document.getElementById('test'))
</script>

JSX方式

JSX方式就是js创建虚拟DOM的语法糖

<div id="test"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel" > /* 此处一定要写babel */
	//1.创建虚拟DOM
	const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
		<h1 id="title">
			<span>Hello,React</span>
		</h1>
	)
	//2.渲染虚拟DOM到页面
	ReactDOM.render(VDOM,document.getElementById('test'))
</script>

image.png

虚拟DOM与真实DOM

打印输出虚拟DOM和真实DOM进行比较

const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
	<h1 id="title">
		<span>Hello,React</span>
	</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))

const TDOM = document.getElementById('demo')
console.log('虚拟DOM',VDOM);
console.log('真实DOM',TDOM);
debugger;

image.png

image.png

  1. 虚拟DOM本质是Object类型的对象(一般对象)
  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性
  3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

JSX入门

概述 全称: JavaScript XML React定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement(component, props, ...children)方法的语法糖

作用: 用来简化创建虚拟DOM

写法:var ele =

Hello JSX!

注意1:它不是字符串, 也不是HTML/XML标签

注意2:它最终产生的就是一个JS对象

标签名任意: HTML标签或其它标签

标签属性任意: HTML标签属性或其它 基本语法规则

  • 定义虚拟DOM时,不要写引号。
  • 标签中混入JS表达式时要用 { }。
  • 样式的类名指定不要用 class,要用 className。(因为class是ES6中类的关键字,所以不让用)
  • 内联样式,要用 style={{ key:value }} 的形式去写。
  • 只有一个根标签
  • 标签必须闭合
  • 标签首字母
  • (1). 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
  • (2). 若大写字母开头,React就去渲染对应的组件,若组件没有定义,则报错。 补充】 区分js表达式与js语句 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方 下面这些都是表达式:
  1. a
  2. a+b
  3. demo(1) // 函数调用表达式
  4. arr.map()
  5. function test () {}
  6. 语句(代码):
  7. 下面这些都是语句(代码):
  8. if(){ }
  9. for(){ }
  10. switch( ){case:xxxx}

总结

  1. 遇到 < 开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
  2. 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含

基本理解和使用

函数式组件

// 1. 创建函数式组件
function MyComponent(){
  console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
  return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))

image.png 执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?

  1. React解析组件标签,找到了MyComponent组件。
  2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

补充】严格模式中的this

function sayThis() {
  console.log(this)
}
sayThis() // Window {...}

function sayThis2() {
  'use strict'
  console.log(this)
}
sayThis2() // undefined
  • 如果不开启严格模式,直接调用函数,函数中的this指向window
  • 如果开启了严格模式,直接调用函数,函数中的this是undefined

类式组件

//1.创建类式组件
class MyComponent extends React.Component {
  render(){
	//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
	//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
	console.log('render中的this:',this);
	return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
  }
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))

执行了ReactDOM.render(.......之后,发生了什么?

React解析组件标签,找到了MyComponent组件。 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

【补充】关于ES6中类的注意事项

  1. 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
  3. 类中所定义的方法,都放在了类的原型对象上,供实例去使用。

注意

  1. 组件名必须首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签
  • 重点关注下渲染类组件标签的基本流程
  1. React内部会创建组件实例对象
  2. 调用render()得到虚拟DOM, 并解析为真实DOM
  3. 插入到指定的页面元素内部

组件实例的三大核心属性1: state 状态

理解

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

补充】类中方法的this指向问题

  1. 类中定义的方法,在内部默认开启了局部的严格模式\
  2. 开启严格模式,函数如果直接调用,this不会指向window,而是undefined
class Person {
  constructor(name,age){
	this.name = name
	this.age = age
  }
  study(){
	//study方法放在了哪里?——类的原型对象上,供实例使用
	//通过Person实例调用study时,study中的this就是Person实例
	console.log(this);
  }
}

const p1 = new Person('tom',18)
p1.study() //通过实例调用study方法  Person
const x = p1.study
x() // 直接调用 undefined

注意

  • 组件中render方法中的this为组件实例对象
  • 组件自定义的方法中this为undefined,如何解决?
  • a) 强制绑定this: 通过函数对象的bind()
  • b) 箭头函数 + 赋值语句
  • 状态数据state,不能直接修改或更新
  • 状态必须通过setState()进行更新, 且更新是一种合并,不是替换。

组件实例的三大核心属性2: props

理解

  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所有属性都保存在props

作用

  1. 通过标签属性从组件向组件传递变化的数据
  2. 注意: 组件内部不可修改props数据,是只读的

使用指南

内部读取某个属性值

this.props.name

扩展属性: 将对象的所有属性通过props传递(批量传递标签属性)

ReactDOM.render(<Person name="yk" age="18" sex="男"/>, document.getElementById('test'))
const person = {name: 'yk', age: 18, sex: '男'} ReactDOM.render(<Person { ...person }/>, document.getElementById('test'))

【补充】展开运算符

let arr1 = [1, 3, 5, 7, 9]
let arr2 = [2, 4, 6, 8, 10]
// 1. 展开一个数组
console.log(...arr1); // 1 3 5 7 9
// 2. 连接数组
let arr3 = [...arr1, ...arr2]

// 3. 在函数中使用
function sum(...numbers) {
  return numbers.reduce((preValue, currentValue) => {
    return preValue + currentValue
  })
}
console.log(sum(1, 2, 3, 4)); // 10

// 4. 构造字面量对象时使用展开语法
let person = {
  name: 'tom',
  age: 18
}

// console.log(...person); // 报错,展开运算符不能展开对象
console.log({...person}) // {name: "tom", age: 18}

let person2 = { ...person } // 可以拷贝一个对象
person.name = 'jerry'
console.log(person2); // {name: "tom", age: 18}
console.log(person); // {name: "jerry", age: 18}

// 5. 合并对象
let person3 = {
  ...person,
  name: 'jack',
  address: "地球"
}
console.log(person3); // {name: "jack", age: 18, address: "地球"}

props中的属性值进行类型限制和必要性限制

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
//对标签属性进行类型、必要性的限制
Person.propTypes = {
  name:PropTypes.string.isRequired, // 限制name必传,且为字符串
  sex:PropTypes.string, // 限制sex为字符串
  age:PropTypes.number, // 限制age为数值
  speak:PropTypes.func, // 限制speak为函数
}

可以写在类的里面,前面加static关键字

默认属性值

//指定默认标签属性值
Person.defaultProps = {
	sex:'男', // sex默认值为男
	age:18 //age默认值为18
}

组件类的构造函数

constructor(props){ super(props) console.log(this.props)//打印所有属性 }

image.png

构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props

很多同学不知道这个为什么药使用super,是为了传递属性

函数式组件使用props

//创建组件
function Person (props){
  const {name,age,sex} = props
    return (
	  <ul>
	    <li>姓名:{name}</li>
	    <li>性别:{sex}</li>
	    <li>年龄:{age}</li>
	  </ul>
	)
}
Person.propTypes = {
  name:PropTypes.string.isRequired, //限制name必传,且为字符串
  sex:PropTypes.string,  //限制sex为字符串
  age:PropTypes.number,  //限制age为数值
}

// 指定默认标签属性值
Person.defaultProps = {
  sex:'男',// sex默认值为男
  age:18 // age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))

refs与事件处理

理解

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

编码

字符串形式的ref(新版本不推荐使用了)

<input ref="input1"/>
this.refs.input1

示例

//创建组件
class Demo extends React.Component{
  //展示左侧输入框的数据
  showData = ()=>{
	const {input1} = this.refs
	alert(input1.value)
  }
  //展示右侧输入框的数据
  showData2 = ()=>{
	const {input2} = this.refs
	alert(input2.value)
  }
  
  render(){
	return(
	  <div>
	    <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
	    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
	    <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
	  </div>
	)
  }
}
//渲染组件到页面
ReactDOM.render(<Demo />,document.getElementById('test'))

回调形式的ref

<input ref={(currentNode)=>{this.input1 = currentNode}} />
//简写
<input ref={ c => this.input1 = c } />

示例

//创建组件
class Demo extends React.Component{
  //展示左侧输入框的数据
  showData = ()=>{
	const {input1} = this
	alert(input1.value)
  }
  //展示右侧输入框的数据
  showData2 = ()=>{
	const {input2} = this
	alert(input2.value)
  }
  
  render(){
	return(
	  <div>
		<input ref={ c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>&nbsp;
		<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
		<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
	  </div>
	)
  }
}
//渲染组件到页面
ReactDOM.render(<Demo />,document.getElementById('test'))

回调执行次数

内联的回调,渲染时调用一次,每次更新都会执行两次
类绑定的回调,就在初始渲染时调用一次 影响不大,日常开发基本都用内联,方便一点

createRef创建ref容器

// React.createRef调用后可以返回一个容器 // 该容器可以存储被ref所标识的节点,该容器是“专人专用”的 
myRef = React.createRef() <input ref={this.myRef}/>

使用

this.myRef.current

//创建组件
class Demo extends React.Component{

  myRef = React.createRef()
  myRef2 = React.createRef()
  //展示左侧输入框的数据
  showData = ()=>{
	alert(this.myRef.current.value);
  }
  //展示右侧输入框的数据
  showData2 = ()=>{
	alert(this.myRef2.current.value);
  }
  
  render(){
	return(
	  <div>
		<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
		<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
		<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
	  </div>
	)
  }
}
//渲染组件到页面
ReactDOM.render(<Demo />,document.getElementById('test'))

React中的事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
  2. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件----为了更好的兼容性
  3. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ----为了的高效
  4. 通过event.target得到发生事件的DOM元素对象----不要过度使用ref

收集表单数据

理解

  1. 受控组件
  2. 非受控组件

非受控组件

页面中所有输入类DOM的值,都是现用现取的

// 创建组件
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" />
        密码:<input ref={c => this.password = c} type="password" name="password" />
        <button>登录</button>  
      </form>
    )
  }
}
// 渲染组件
ReactDOM.render(<Login />, document.getElementById('test'))

受控组件

页面中输入类的DOM,随着输入的过程,将数据存储在状态state中,需要用的时候在从状态state中取(有点类似Vue中的双向数据绑定)

// 创建组件
class Login extends React.Component {
  // 初始化状态
  state = {
    username: '',
    password: ''
  }
  // 保存用户名到状态中
  saveUsername = (event) => {
    this.setState({username: event.target.value})
  }
  // 保存密码到状态中
  savePassword = (event) => {
    this.setState({password: event.target.value})
  }
  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault()
    const {username, password} = this.state
    alert(`您输入的用户名是 ${username},您输入的密码是:${password}`)
  }

  render() {
    return (
      <form action="https://www.baidu.com/" onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveUsername} type="text" name="username" />
        密码:<input onChange={this.savePassword} type="password" name="password" />
        <button>登录</button>  
      </form>
    )
  }
}
// 渲染组件
ReactDOM.render(<Login />, document.getElementById('test'))

高阶函数与函数的柯里化

高阶函数

  • 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
  • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
  • 常见的高阶函数有:Promise、setTimeout、arr.map()等等

函数的柯里化

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

function sum1(a, b, c){
  return a + b + c;
}
sum1(1, 2, 3)

// 柯里化后
function sum(a){
  return(b)=>{
	return (c)=>{
	  return a+b+c
	}
  }
}
sum(1)(2)(3)
//创建组件
class Login extends React.Component{
  //初始化状态
  state = {
    username:'', //用户名
    password:'' //密码
  }

  //保存表单数据到状态中 (高阶函数+函数柯里化)
  saveFormData = (dataType)=>{
	return (event)=>{
	  this.setState({[dataType]:event.target.value})
	}
  }

  //表单提交的回调
  handleSubmit = (event)=>{
	event.preventDefault() //阻止表单提交
	const {username,password} = this.state
	alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
  }
  render(){
	return(
	  <form onSubmit={this.handleSubmit}>
		用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
		密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
		<button>登录</button>
	  </form>
	)
  }
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))

组件的生命周期

理解

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

引入案例

需求:定义组件实现以下功能:

  1. 让指定的文本做显示 / 隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时2S
  3. 点击“不活了”按钮从界面中卸载组件
//创建组件
//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Life extends React.Component{

  state = {opacity:1}

  death = ()=>{
	//卸载组件
	ReactDOM.unmountComponentAtNode(document.getElementById('test'))
  }

  //组件挂完毕
  componentDidMount(){
	console.log('componentDidMount');
	this.timer = setInterval(() => {
	  //获取原状态
	  let {opacity} = this.state
	  //减小0.1
	  opacity -= 0.1
	  if(opacity <= 0) opacity = 1
	  //设置新的透明度
	  this.setState({opacity})
	}, 200);
  }

  //组件将要卸载
  componentWillUnmount(){
	//清除定时器
	clearInterval(this.timer)
  }

  //初始化渲染、状态更新之后
  render(){
	console.log('render');
	return(
	  <div>
	    <h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
	    <button onClick={this.death}>不活了</button>
	  </div>
	)
  }
}
//渲染组件
ReactDOM.render(<Life/>,document.getElementById('test'))

生命周期的三个阶段(旧)

20210608114316605.png 初始化阶段

  • 由ReactDOM.render()触发—初次渲染
  • constructor() —— 类组件中的构造函数
  • componentWillMount() —— 组件将要挂载 【即将废弃】
  • render() —— 挂载组件
  • componentDidMount() —— 组件挂载完成 比较常用
  • 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
class Count extends React.Component{
  // 构造器
  constructor(props){
    alert('constructor')
	console.log('Count---constructor');
	super(props)
	//初始化状态
	this.state = {count:0}
  }
  add = () => {
    const {count} = this.state
    this.setState({count: count+1})
  }
  
  //组件将要挂载的钩子
  componentWillMount(){
    alert('componentWillMount')
    console.log('Count---componentWillMount');
  }

  render(){
    alert('render')
    console.log('Count---render');
    const {count} = this.state
    return(
      <div>
        <h1>当前计数值:{count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    )
  }

  //组件挂载完毕的钩子
  componentDidMount(){
    alert('componentDidMount')
	console.log('Count---componentDidMount');
  }
}
ReactDOM.render(<Count/>, document.getElementById('test'))

更新阶段【第一种情况】父组件重新render触发

componentWillReceiveProps() —— 接收属性参数(非首次)【即将废弃】 然后调用下面的钩子函数

【第二种情况】由组件内部this.setSate()

shouldComponentUpdate() —— 组件是否应该被更新(默认返回true) 然后调用下面的钩子函数

【第三种情况】强制更新 forceUpdate()

componentWillUpdate() ——组件将要更新 【即将废弃】 render() —— 组件更新 componentDidUpdate() —— 组件完成更新

卸载组件

ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount() —— 组件即将卸载

生命周期的三个阶段(新)

image.png

更新阶段

  • 由组件内部this.setSate()或父组件重新render触发或强制更新forceUpdate()
  • getDerivedStateFromProps() —— 从props得到一个派生的状态 【新增】
  • shouldComponentUpdate() —— 组件是否应该被更新(默认返回true)
  • render() —— 挂载组件
  • getSnapshotBeforeUpdate() —— 在更新之前获取快照【新增】
  • componentDidUpdate(prevProps, prevState, snapshotValue) —— 组件完成更新
  1. 卸载组件
  • 由ReactDOM.unmountComponentAtNode()触发
  • componentWillUnmount() —— 组件即将卸载

重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

虚拟DOM与DOM Diffing算法

基本原理图

image.png

关于key的经典面试题

react/vue中的key有什么作用?(key的内部原理是什么?) 为什么遍历列表时,key最好不要用index? 虚拟DOM中key的作用 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    • (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
    • (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
  • 旧虚拟DOM中未找到与新虚拟DOM相同的key
    • 根据数据创建新的真实DOM,随后渲染到到页面

用index作为key可能会引发的问题

若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低

如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==> 界面有问题

注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作, 仅用于渲染列表用于展示,使用index作为key是没有问题的

SPA的理解

  1. 单页Web应用(single page web application,SPA
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

路由的理解

什么是路由?

  1. 一个路由就是一个映射关系(key: value)
  2. key为路径, value可能是functioncomponent

路由分类

后端路由

  1. 理解: valuefunction, 用来处理客户端提交的请求。
  2. 注册路由: router.get(path, function(req, res))
  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由

  1. 浏览器端路由,value是component,用于展示页面内容。
  2. 注册路由: <Route path="/test" component={Test}>
  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

 react-router-dom 的理解

  1. React的一个插件库。
  2. 专门用来实现一个SPA应用。
  3. 基于React的项目基本都会用到此库。

react-router-dom相关API

内置组件

  1. <BrowserRouter>
  2. <HashRouter>
  3. <Route>
  4. <Redirect>
  5. <Link>
  6. <NavLink>
  7. <Switch>

其它

  1. history对象
  2. match对象
  3. withRouter函数

基本路由使用

效果

2021061416445215.gif

准备

  1. 下载react-router-dom: npm install react-router-dom
    在这里插入图片描述
  2. 引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">

路由的基本使用

明确好界面中的导航区、展示区 导航区的a标签改为Link标签 <Link to="/xxxxx">Demo</Link> 展示区写Route标签进行路径的匹配

<Route path='/xxxx' component={Demo}/>
<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>

实现

index.js

这里用一个标签将整个App包起来,保证使用的是同一个路由器

//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//
import {BrowserRouter} from 'react-router-dom'
//引入App
import App from './App'

ReactDOM.render(
	<BrowserRouter>
		<App/>
	</BrowserRouter>,
	document.getElementById('root')
)

App.jsx

import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Home from './Home'
import About from './About'

export default class App extends Component {
  render() {
    return (
      <div>
      <div className="row">
        <div className="col-xs-offset-2 col-xs-8">
          <div className="page-header"><h2>React Router Demo</h2></div>
        </div>
      </div>
      <div className="row">
        <div className="col-xs-2 col-xs-offset-2">
          <div className="list-group">
            {/* 原生html中,靠<a>跳转不同的页面 */}
            {/* <a className="list-group-item" href="./about.html">About</a>
            <a className="list-group-item active" href="./home.html">Home</a> */}

            {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
            <Link className="list-group-item" to="/about">About</Link>
            <Link className="list-group-item" to="/home">Home</Link>
          </div>
        </div>
        <div className="col-xs-6">
          <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Route path='/about' component={About} />
                <Route path='/home' component={Home} />
            </div>
          </div>
        </div>
      </div>
    </div>
    )
  }
}

路由组件与一般组件的区别

  • 写法不同: 一般组件: 路由组件:

  • 存放位置不同: 一般组件:components

    路由组件:pages

  • 接收到的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与封装NavLink NavLink可以实现路由链接的高亮,通过activeClassName属性指定样式名,默认是"active"

<NavLink activeClassName="demo" className="list-group-item" to="/home">Home</NavLink> 可以自己封装一个NavLink【一般组件】

import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
	// console.log(this.props);
	return (
	  <NavLik activeClassName="demo" className="list-group-item" {...this.props} />
	)
  }
}

标签体内容是特殊的标签属性通过this.props.children可以获取标签体内容
使用 <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink>

Switch的使用

  1. 通常情况下,path和component是一一对应的关系。
  2. Switch可以提高路由匹配效率(单一匹配)。
<Switch>
  <Route path="/about" component={About}/>
  <Route path="/home" component={Home}/>
  <Route path="/home" component={Test}/>
</Switch>

这样只要匹配到了第一个就不会再往下匹配了

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

  1. public/index.html 中 引入样式时不写 ./ 写 / (常用)【绝对路径】
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
  3. 使用HashRouter

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

  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>

嵌套路由使用

效果

20210614220947211.gif

注意

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

实现

Home/index.jsx

import React, { Component } from 'react'
import { Route, NavLink,Redirect,Switch } from 'react-router-dom'
import News from './News'
import Message from './Message'

export default class Home extends Component {
  render() {
    return (
      <div>
        <h3>我是Home的内容</h3>
        <div>
          <ul className="nav nav-tabs">
            <li>
              <NavLink className="list-group-item" to="/home/news">News</NavLink>
            </li>
            <li>
              <NavLink className="list-group-item" to="/home/message">Message</NavLink>
            </li>
          </ul>
          <Switch>
            <Route path='/home/news' component={News} />
            <Route path='/home/message' component={Message} />
            <Redirect to='/home/news' />
          </Switch>
        </div>
      </div>
    )
  }
}

向路由组件传递参数数据

效果

20210614225203116.gif

具体方法

方法1. params参数 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link> 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/> 接收参数:<Route path="/demo/test/:name/:age" component={Test}/>

Message/index.jsx

import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom';
import Detail from './Detail';

export default class Message extends Component {
  state = {
    messageArr: [
      { id: '01', title: '消息1' },
      { id: '02', title: '消息2' },
      { id: '03', title: '消息3' },
    ]
  }
  render() {
    const { messageArr } = this.state;
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递params参数 */}
                  <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr />
        {/* 声明接收params参数 */}
        <Route path="/home/message/detail/:id/:title" component={Detail}/>
      </div>
    )
  }
}

Detail/index.jsx

20210614224952124.png

import React, { Component } from 'react'

export default class Detail extends Component {
  state = {
    detailData : [
      { id: '01', content: '你好啊' },
      { id: '02', content: '还不错鸭' },
      { id: '03', content: '显示我吧' }
    ]
  }
  render() {
    console.log(this.props)
    // 接收params参数
    const { id, title } = this.props.match.params
    const findResult= this.state.detailData.find((dataObj) => {
      return dataObj.id === id
    })
    return (
      <div>
        <ul>
          <li>ID: {id }</li>
          <li>Title: {title }</li>
          <li>Content: { findResult.content}</li>
        </ul>
      </div>
    )
  }
}

方法2. search参数 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情 注册路由(无需声明,正常注册即可): 接收参数:this.props.location.search 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

import qs from 'querystring'
let obj = {name:'tom', age:18}
console.log(qs.stringify(obj)) // name=tom&age=18
let str = 'carName=Benz&price=199'
console.log(qs.parse(str)) // {carName: 'Benz', price: 199}

方法3. state参数 路由链接(携带参数):<Link to={{ pathname:'/demo/test', state:{name:'tom',age:18} }}>详情 注册路由(无需声明,正常注册即可): 接收参数:this.props.location.state 备注:刷新也可以保留住参数【history对象记录着在】

Message/index.jsx

export default class Message extends Component {
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj)=>{
              return (
                <li key={msgObj.id}>

                  {/* 向路由组件传递params参数 */}
                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

                  {/* 向路由组件传递search参数 */}
                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

                  {/* 向路由组件传递state参数 */}
                  <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 声明接收params参数 */}
        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

        {/* search参数无需声明接收,正常注册路由即可 */}
        {/* <Route path="/home/message/detail" component={Detail}/> */}

        {/* state参数无需声明接收,正常注册路由即可 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Detail/index.jsx

import React, { Component } from 'react'
// import qs from 'querystring'

export default class Detail extends Component {
  render() {
    console.log(this.props);

    // 接收params参数
    // const {id,title} = this.props.match.params 

    // 接收search参数
    // const {search} = this.props.location
    // const {id,title} = qs.parse(search.slice(1))

    // 接收state参数
    const {id,title} = this.props.location.state || {}

    const findResult = DetailData.find((detailObj)=>{
      return detailObj.id === id
    }) || {}
    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
        <li>CONTENT:{findResult.content}</li>
      </ul>
    )
  }
}

多种路由跳转方式

编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退

  • this.prosp.history.push()
  • this.prosp.history.replace()
  • this.prosp.history.goBack()
  • this.prosp.history.goForward()
  • this.prosp.history.go()

withRouter的使用 export default withRouter(Header) withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件

注意

BrowserRouter与HashRouter的区别

底层原理不一样:

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

redux理解

相关文档

中文文档: www.redux.org.cn/

Github: github.com/reactjs/red… 先把它装上 npm i redux

redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态

什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)
  2. 一个组件需要改变另一个组件的状态(通信)
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用

redux工作流程

20210615225020695.png

redux的三个核心概念

action

  1. 动作的对象

  2. 包含 2 个属性

    • type:标识属性, 值为字符串, 唯一, 必要属性
    • data:数据属性, 值类型任意, 可选属性
  3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的stateaction, 产生新的state的纯函数。

store

将state、action、reducer联系在一起的对象

如何得到此对象?

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)

此对象的功能?

  • getState(): 得到state
  • dispatch(action): 分发action, 触发reducer调用, 产生新的state
  • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

API

createStore()

作用:创建包含指定reducer的store对象

store对象

作用: redux库最核心的管理对象 它内部维护着:

  • state
  • reducer 核心方法: getState() dispatch(action) subscribe(listener) 具体编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)
applyMiddleware()

作用:应用上基于redux的中间件(插件库)

combineReducers()

作用:合并多个reducer函数

使用redux编写应用

效果

20210617232712887.gif

实现

redux/store.js

该文件专门用于暴露一个store对象,整个应用只有一个store对象

/ 引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux";
// 引入为Count组件服务的reducer
import countReducer from "./count_reducer.js";
// 暴露store
export default createStore(countReducer);

redux/count_reducer.js

该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)

import {INCREMENT,DECREMENT} from './constant'

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
  // console.log(preState);
  //从action对象中获取:type、data
  const {type,data} = action
  //根据type决定如何加工数据
  switch (type) {
    case INCREMENT: //如果是加
      return preState + data
    case DECREMENT: //若果是减
      return preState - data
    default:
      return preState
  }
}

redux/count_action.js

该文件专门为Count组件生成action对象

import {INCREMENT,DECREMENT} from './constant'

export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

redux/constant.js

该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错 export const INCREMENT = 'increment' export const DECREMENT = 'decrement'

Count/index.jsx

import React, { Component } from 'react'
//引入store,用于获取redux中保存状态
import store from '../../redux/store'
//引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'

export default class Count extends Component {

  /* componentDidMount(){
    //检测redux中状态的变化,只要变化,就调用render
    store.subscribe(()=>{
      this.setState({})
    })
  } */

  //加法
  increment = ()=>{
    const {value} = this.selectNumber
    store.dispatch(createIncrementAction(value*1))
  }
  //减法
  decrement = ()=>{
    const {value} = this.selectNumber
    store.dispatch(createDecrementAction(value*1))
  }
  //奇数再加
  incrementIfOdd = ()=>{
    const {value} = this.selectNumber
    const count = store.getState()
    if(count % 2 !== 0){
      store.dispatch(createIncrementAction(value*1))
    }
  }
  //异步加
  incrementAsync = ()=>{
    const {value} = this.selectNumber
    setTimeout(()=>{
      store.dispatch(createIncrementAction(value*1))
    },500)
  }

  render() {
    return (
      <div>
        <h1>当前求和为:{store.getState()}</h1>
        <select ref={c => this.selectNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>&nbsp;
      </div>
    )
  }
}

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'

ReactDOM.render(<App/>,document.getElementById('root'))

// 监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(()=>{
	ReactDOM.render(<App/>,document.getElementById('root'))
})

总结

store.js:

1). 引入redux中的createStore函数,创建一个store 2). createStore调用时要传入一个为其服务的reducer 3). 记得暴露store对象

count_reducer.js:

  • reducer的本质是一个函数,接收:preState,action,返回加工后的状态
  • reducer有两个作用:初始化状态,加工状态
  • reducer被第一次调用时,是store自动触发的,
  • 传递的preState是undefined,
  • 传递的action是:{type:'@@REDUX/INIT_a.2.b.4}
  • 在index.js中监测store中状态的改变,一旦发生改变重新渲染
  • 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

完整版新增文件:

  • count_action.js 专门用于创建action对象
  • constant.js 放置容易写错的type值

redux异步编程

理解

  1. redux默认是不能进行异步处理的,
  2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

使用异步中间件

npm install redux-thunk

count_action.js

该模块专门为Count组件生成action对象 异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的

import {INCREMENT,DECREMENT} from './constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(createIncrementAction(data))
		},time)
	}
}

store.js

该模块专门用于暴露一个store对象,整个应用只有一个store对象

在这里引入中间件

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))

总结

  1. 明确:延迟的动作不想交给组件自身,想交给action
  2. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
  3. 具体编码:
  4. npm install redux-thunk,并配置在store中
  5. 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
  6. 异步任务有结果后,分发一个同步的action去真正操作数据。
  7. 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

react-redux

理解

  1. 一个React插件库
  2. 专门用来简化React应用中使用redux

 react-redux将所有组件分成两大类

UI组件 1. 2. 只负责 UI 的呈现,不带有任何业务逻辑 3. 通过props接收数据(一般数据和函数) 4. 不使用任何 Redux 的 API 5. 一般保存在components文件夹下 容器组件

  1. 负责管理数据和业务逻辑,不负责UI的呈现
  2. 使用 Redux 的 API
  3. 一般保存在containers文件夹下

20210617234345767.png 相关API

  • Provider:让所有组件都可以得到state数据
  • connect:用于包装 UI 组件生成容器组件
  • mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
  • mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

基本使用

安装

npm install react-redux

20210618095304942.png

components/Count/index.jsx

UI组件

import React, { Component } from 'react'

export default class Count extends Component {
  //加法
  increment = ()=>{
    const {value} = this.selectNumber
    this.props.jia(value*1)
  }
  //减法
  decrement = ()=>{
    const {value} = this.selectNumber
    this.props.jian(value*1)
  }

  //奇数再加
  incrementIfOdd = ()=>{
    const {value} = this.selectNumber
    if(this.props.count % 2 !== 0){
      this.props.jia(value*1)
    }
  }
  //异步加
  incrementAsync = ()=>{
    const {value} = this.selectNumber
    this.props.jiaAsync(value*1,500)
  }

  render() {
    //console.log('UI组件接收到的props是',this.props);
    return (
      <div>
        <h1>当前求和为:{this.props.count}</h1>
        <select ref={c => this.selectNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>&nbsp;
      </div>
    )
  }
}

containers/Count/index.jsx

容器组件

//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
  createIncrementAction,
  createDecrementAction,
  createIncrementAsyncAction
} from '../../redux/count_action'

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 
  1.mapStateToProps函数返回的是一个对象;
  2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
  3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
  return {count:state}
}

/* 
  1.mapDispatchToProps函数返回的是一个对象;
  2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
  3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
  return {
    jia:number => dispatch(createIncrementAction(number)),
    jian:number => dispatch(createDecrementAction(number)),
    jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
  }
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

App.jsx

给容器组件传递store

import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'

export default class App extends Component {
  render() {
    return (
      <div>
        {/* 给容器组件传递store */}
        <Count store={store} />
      </div>
    )
  }
}

总结 明确两个概念:

  • UI组件:不能使用任何 redux 的api,只负责页面的呈现、交互等。
  • 容器组件:负责和 redux 通信,将结果交给UI组件。
  • 如何创建一个容器组件————靠 react-redux 的 connect 函数
  • connect(mapStateToProps,mapDispatchToProps)(UI组件)
    • mapStateToProps: 映射状态,返回值是一个对象
    • mapDispatchToProps: 映射操作状态的方法,返回值是一个对象
  • 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
  • 备注2:mapDispatchToProps,也可以是一个对象【优化1】

优化

优化1 简写mapDispatchToProps

//使用connect()()创建并暴露一个Count的容器组件

export default connect(
  state => ({count:state}),

  //mapDispatchToProps的一般写法
  /* dispatch => ({
    jia:number => dispatch(createIncrementAction(number)),
    jian:number => dispatch(createDecrementAction(number)),
    jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
  }) */

  //mapDispatchToProps的简写
  {
    jia:createIncrementAction,
    jian:createDecrementAction,
    jiaAsync:createIncrementAsyncAction,
  }
)(CountUI)

**容器组件可以检测redux中的状态改变,并渲染页面,所以不需要在index.js中检测了 不要在App.jsx中给子组件传递store了 **

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'
ReactDOM.render(
	<Provider store={store}>
		<App/>
	</Provider>,
	document.getElementById('root')
)

优化3 整合UI组件和容器组件

每个组件两个文件夹太麻烦了,直接整合在一起就好了~

containers/Count/index.jsx

import React, { Component } from 'react'
//引入action
import {
  createIncrementAction,
  createDecrementAction,
  createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

  state = {carName:'奔驰c63'}

  //加法
  increment = ()=>{
    const {value} = this.selectNumber
    this.props.jia(value*1)
  }
  //减法
  decrement = ()=>{
    const {value} = this.selectNumber
    this.props.jian(value*1)
  }
  //奇数再加
  incrementIfOdd = ()=>{
    const {value} = this.selectNumber
    if(this.props.count % 2 !== 0){
      this.props.jia(value*1)
    }
  }
  //异步加
  incrementAsync = ()=>{
    const {value} = this.selectNumber
    this.props.jiaAsync(value*1,500)
  }

  render() {
    //console.log('UI组件接收到的props是',this.props);
    return (
      <div>
        <h1>当前求和为:{this.props.count}</h1>
        <select ref={c => this.selectNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>&nbsp;
      </div>
    )
  }
}

使用connect()()创建并暴露一个Count的容器组件

export default connect(
  state => ({count:state}),

  //mapDispatchToProps的一般写法
  /* dispatch => ({
    jia:number => dispatch(createIncrementAction(number)),
    jian:number => dispatch(createDecrementAction(number)),
    jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
  }) */

  //mapDispatchToProps的简写
  {
    jia:createIncrementAction,
    jian:createDecrementAction,
    jiaAsync:createIncrementAsyncAction,
  }
)(Count)

总结

  • 容器组件和UI组件整合一个文件
  • 无需自己给容器组件传递store,给包裹一个<Provider store={store}>即可。
  • 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
  • mapDispatchToProps也可以简单的写成一个对象
  • 一个组件要和redux“打交道”要经过哪几步?
  • 定义好UI组件—不暴露
  • 引入connect生成一个容器组件,并暴露,写法如下:
connect(
	state => ({key:value}), //映射状态
	{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
  1. 在UI组件中通过this.props.xxxxxxx读取和操作状态

store.js

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore, applyMiddleware, combineReducers } from "redux";
//引入为Count组件服务的reducer
import countReducer from "./reducers/count";
//引入为Count组件服务的reducer
import personReducer from "./reducers/person";
//引入redux-thunk,用于支持异步action
import thunk from "redux-thunk";

// 汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
  he: countReducer,
  rens: personReducer,
});

//暴露store
export default createStore(allReducer, applyMiddleware(thunk));

总结

  1. 定义一个Pserson组件,和Count组件通过redux共享数据。
  2. 为Person组件编写:reducer、action,配置constant常量。
  3. 重点:Person的reducer和Count的reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
  4. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。 纯函数和高阶函数 reducer要求是一个纯函数,所以操作数组的时候,不能用push之类的方法

纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 必须遵守以下一些约束
  3. 不得改写参数数据
  4. 不会产生任何副作用,例如网络请求,输入和输出设备
  5. 不能调用Date.now()或者Math.random()等不纯的方法
  6. redux的reducer函数必须是一个纯函数 高阶函数
  7. 理解: 一类特别的函数
  8. 情况1: 参数是函数
  9. 情况2: 返回是函数
  10. 常见的高阶函数:
  11. 定时器设置函数
  12. 数组的forEach()/map()/filter()/reduce()/find()/bind()
  13. promise
  14. react-redux中的connect函数
  15. 作用: 能实现更加动态, 更加可扩展的功能

最终版

  1. 所有变量名字要规范,尽量触发对象的简写形式。
  2. reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

202106181534064.png

containers/Count/index.jsx

import React, { Component } from 'react'
//引入action
import {
  increment,
  decrement,
  incrementAsync
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

  //加法
  increment = ()=>{
    const {value} = this.selectNumber
    this.props.increment(value*1)
  }
  //减法
  decrement = ()=>{
    const {value} = this.selectNumber
    this.props.decrement(value*1)
  }
  //奇数再加
  incrementIfOdd = ()=>{
    const {value} = this.selectNumber
    if(this.props.count % 2 !== 0){
      this.props.increment(value*1)
    }
  }
  //异步加
  incrementAsync = ()=>{
    const {value} = this.selectNumber
    this.props.incrementAsync(value*1,500)
  }

  render() {
    //console.log('UI组件接收到的props是',this.props);
    return (
      <div>
        <h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
        <h4>当前求和为:{this.props.count}</h4>
        <select ref={c => this.selectNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>&nbsp;
      </div>
    )
  }
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
  state => ({
    count:state.count,
    personCount:state.persons.length
  }),
  {increment,decrement,incrementAsync}
)(Count)

containers/Person/index.jsx

import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {addPerson} from '../../redux/actions/person'

class Person extends Component {

  addPerson = ()=>{
    const name = this.nameNode.value
    const age = this.ageNode.value*1
    const personObj = {id:nanoid(),name,age}
    this.props.addPerson(personObj)
    this.nameNode.value = ''
    this.ageNode.value = ''
  }

  render() {
    return (
      <div>
        <h2>我是Person组件,上方组件求和为{this.props.count}</h2>
        <input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
        <input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
        <button onClick={this.addPerson}>添加</button>
        <ul>
          {
            this.props.persons.map((p)=>{
              return <li key={p.id}>{p.name}--{p.age}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

export default connect(
  state => ({
    persons:state.persons,
    count:state.count
  }),//映射状态
  {addPerson}//映射操作状态的方法
)(Person)

redux/actions/count.js

/* 
  该文件专门为Count组件生成action对象
*/
import { INCREMENT, DECREMENT } from "../constant";

//同步action,就是指action的值为Object类型的一般对象
export const increment = (data) => ({ type: INCREMENT, data });
export const decrement = (data) => ({ type: DECREMENT, data });

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const incrementAsync = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment(data));
    }, time);
  };
};

redux/action/person.js

import { ADD_PERSON } from "../constant";
//创建增加一个人的action动作对象
export const addPerson = (personObj) => ({ type: ADD_PERSON, data: personObj });

redux/reducers/count.js

/* 
  1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
  2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREMENT, DECREMENT } from "../constant";

const initState = 0; //初始化状态
export default function countReducer(preState = initState, action) {
  // console.log('countReducer@#@#@#');
  //从action对象中获取:type、data
  const { type, data } = action;
  //根据type决定如何加工数据
  switch (type) {
    case INCREMENT: //如果是加
      return preState + data;
    case DECREMENT: //若果是减
      return preState - data;
    default:
      return preState;
  }
}

redux/reducers/person.js

import { ADD_PERSON } from "../constant";

//初始化人的列表
const initState = [{ id: "001", name: "tom", age: 18 }];

export default function personReducer(preState = initState, action) {
  const { type, data } = action;
  switch (type) {
    case ADD_PERSON: //若是添加一个人
      //preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
      return [data, ...preState];
    default:
      return preState;
  }
}

redux/reducers/index.js

/* 
	该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import { combineReducers } from "redux";
//引入为Count组件服务的reducer
import count from "./count";
//引入为Person组件服务的reducer
import persons from "./person";

//汇总所有的reducer变为一个总的reducer
export default combineReducers({
  count,
  persons,
});

redux/constant.js

/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
export const ADD_PERSON = "add_person";

redux/store.js

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore, applyMiddleware } from "redux";
//引入汇总之后的reducer
import reducer from "./reducers";
//引入redux-thunk,用于支持异步action
import thunk from "redux-thunk";
//引入redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension";

//暴露store
export default createStore(
  reducer,
  composeWithDevTools(applyMiddleware(thunk))
);

App.jsx

import React, { Component } from 'react'
import Count from './containers/Count' //引入的Count的容器组件
import Person from './containers/Person' //引入的Person的容器组件

export default class App extends Component {
  render() {
    return (
      <div>
        <Count/>
        <hr/>
        <Person/>
      </div>
    )
  }
}

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";

ReactDOM.render(
  /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

【React】拓展

  1. setState
  2. 路由组件懒加载
  3. Hooks
  4. Context
  5. PureComponent
  6. 插槽
  7. 错误边界
  8. 组件通信方式
  9.  setState

setState

setState更新状态其实有两种写法
setState这个方法在调用的时候是同步的,但是引起React的状态更新是异步的
【React状态更新是异步的】

对象式

setState(stateChange, [callback])
  1. stateChange为状态改变对象 (该对象可以体现出状态的更改)
  2. callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用【异步更新后调用,可以拿到更新后的状态的值】 之前一直写的,就是直接传一个对象
this.setState({ count: this.state.count+1 })

函数式

setState(updater, [callback])
  1. updater为返回stateChange对象的函数【返回值是对象】
  2. updater可以接收到stateprops
  3. callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用【和对象式是一样的】 传的时候回调函数,可以接收到state和props,函数的返回值是设置状态的对象
setState(state => ({count: state.count+1})) 

总结

  • 对象式的setState是函数式的setState的简写方式【语法糖】
  • 使用原则
  • 如果新状态不依赖于原状态【使用对象方式】
  • 如果新状态依赖于原状态 【使用函数方式】
  • 如果需要在setState()执行后,获取最新的状态数据,可以在第二个callback函数中读取到异步更新的最新值

路由组件的懒加载 lazyLoad

用的时候才加载,一般是路由组件进行懒加载 如果不用路由懒加载,页面在第一次进入的时候,就请求了所有组件的数据,如果组件过多,过多的请求这就没有必要了,应该是用户按哪个链接再请求哪个组件

通过React的lazy函数配合import()函数动态加载路由组件【路由组件代码会被分开打包】 通过指定在加载得到路由打包文件前显示一个自定义loading界

import React, { Component, lazy, Suspense} from 'react'
import {NavLink,Route} from 'react-router-dom'

// import Home from './Home'
// import About from './About'

import Loading from './Loading'
const Home = lazy(()=> import('./Home') )
const About = lazy(()=> import('./About'))

export default class Demo extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
              <NavLink className="list-group-item" to="/about">About</NavLink>
              <NavLink className="list-group-item" to="/home">Home</NavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                <Suspense fallback={<Loading/>}>
                  {/* 注册路由 */}
                  <Route path="/about" component={About}/>
                  <Route path="/home" component={Home}/>
                </Suspense>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

Hooks

React Hook/Hooks是什么? Hook 是 React 16.8 的新增特性。 它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。【函数式组件】

三个常用的Hook

  1. State Hook: React.useState()
  2. Effect Hook: React.useEffect()
  3. Ref Hook: React.useRef() State Hook

State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作 语法: const [xxx, setXxx] = React.useState(initValue)

useState()说明: 参数: 第一次初始化指定的值在内部作缓存 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数

setXxx()2种写法:

setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值 setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

React中的副作用操作:

  • 发ajax请求数据获取
  • 设置订阅 / 启动定时器
  • 手动更改真实DOM

语法和说明:

useEffect(() => { 
  // 在此可以执行任何带副作用操作【挂载+更新】
  return () => { // 在组件卸载前执行
    // 在此做一些收尾工作, 比如清除定时器/取消订阅等【卸载前】
  }
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

可以把 useEffect Hook 看做如下三个函数的组合

componentDidMount()
componentDidUpdate()
componentWillUnmount() 

Ref Hook Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据 语法: const refContainer = useRef() 作用:保存标签对象,功能与React.createRef()一样 代码示例

import React from 'react'
import ReactDOM from 'react-dom'

//类式组件
/* class Demo extends React.Component {

  state = {count:0}

  myRef = React.createRef()

  add = ()=>{
    this.setState(state => ({count:state.count+1}))
  }

  unmount = ()=>{
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }

  show = ()=>{
    alert(this.myRef.current.value)
  }

  componentDidMount(){
    this.timer = setInterval(()=>{
      this.setState( state => ({count:state.count+1}))
    },1000)
  }

  componentWillUnmount(){
    clearInterval(this.timer)
  }

  render() {
    return (
      <div>
        <input type="text" ref={this.myRef}/>
        <h2>当前求和为{this.state.count}</h2>
        <button onClick={this.add}>点我+1</button>
        <button onClick={this.unmount}>卸载组件</button>
        <button onClick={this.show}>点击提示数据</button>
      </div>
    )
  }
} */

function Demo(){
  //console.log('Demo');

  const [count,setCount] = React.useState(0)
  const myRef = React.useRef()

  React.useEffect(()=>{
    let timer = setInterval(()=>{
      setCount(count => count+1 )
    },1000)
    return ()=>{
      clearInterval(timer)
    }
  },[])

  //加的回调
  function add(){
    //setCount(count+1) //第一种写法
    setCount(count => count+1 )
  }

  //提示输入的回调
  function show(){
    alert(myRef.current.value)
  }

  //卸载组件的回调
  function unmount(){
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }

  return (
    <div>
      <input type="text" ref={myRef}/>
      <h2>当前求和为:{count}</h2>
      <button onClick={add}>点我+1</button>
      <button onClick={unmount}>卸载组件</button>
      <button onClick={show}>点我提示数据</button>
    </div>
  )
}

export default Demo

Fragment

文档碎片

使用

React中的副作用操作: 或者 <></>

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

import React, { Component,Fragment } from 'react'

export default class Demo extends Component {
  render() {
    return (
      <Fragment kye={1}>
        <input type="text"/>
        <input type="text"/>
      </Fragment>
    )
  }

Context

上下文

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

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

渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:

<xxxContext.Provider value={数据}>
	子组件
</xxxContext.Provider>
后代组件读取数据:
第一种方式:仅适用于类组件

static contextType = xxxContext  // 声明接收context
this.context // 读取context中的value数据

第二种方式: 函数组件与类组件都可以

<xxxContext.Consumer>
  {
    value => ( // value就是context中的value数据
      要显示的内容
    )
  }
</xxxContext.Consumer>

注意 在应用开发中一般不用context, 一般都用它来封装react插件

import React, { Component } from 'react'
import './index.css'

![20210619182738795.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db5591474b6f460dad4b402991f89a21~tplv-k3u1fbpfcp-watermark.image?)
//创建Context对象
const MyContext = React.createContext()
const { Provider, Consumer } = MyContext

export default class A extends Component {

  state = {username:'tom',age:18}

  render() {
    const {username,age} = this.state
    return (
      <div className="parent">
        <h3>我是A组件</h3>
        <h4>我的用户名是:{username}</h4>
        <Provider value={{username,age}}>
          <B/>
        </Provider>
      </div>
    )
  }
}

class B extends Component {
  render() {
    return (
      <div className="child">
        <h3>我是B组件</h3>
        <C/>
      </div>
    )
  }
}

/* class C extends Component {
  //声明接收context
  static contextType = MyContext
  render() {
    const {username,age} = this.context
    return (
      <div className="grand">
        <h3>我是C组件</h3>
        <h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
      </div>
    )
  }
} */

function C(){
  return (
    <div className="grand">
      <h3>我是C组件</h3>
      <h4>我从A组件接收到的用户名:
      <Consumer>
        {value => `${value.username},年龄是${value.age}`}
      </Consumer>
      </h4>
    </div>
  )
}

组件优化

Component的问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

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

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 重写shouldComponentUpdate()方法

比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

shouldComponentUpdate(nextProps,nextState){
  // console.log(this.props,this.state); //目前的props和state
  // console.log(nextProps,nextState); //接下要变化的目标props,目标state
  return !this.state.carName === nextState.carName
} 

办法2: 使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true

注意

只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false不要直接修改state数据, 而是要产生新数据项目中一般使用PureComponent来优化

import React, { PureComponent } from 'react'
import './index.css'

export default class Parent extends PureComponent {

  state = {carName:"奔驰c36",stus:['小张','小李','小王']}

  addStu = ()=>{
    /* const {stus} = this.state
    stus.unshift('小刘')
    this.setState({stus}) */

    const {stus} = this.state
    this.setState({stus:['小刘',...stus]})
  }

  changeCar = ()=>{
    this.setState({carName:'迈巴赫'})

    // const obj = this.state
    // obj.carName = '迈巴赫'
    // console.log(obj === this.state);
    // this.setState(obj)
  }

  /* shouldComponentUpdate(nextProps,nextState){
    // console.log(this.props,this.state); //目前的props和state
    // console.log(nextProps,nextState); //接下要变化的目标props,目标state
    return !this.state.carName === nextState.carName
  } */

  render() {
    console.log('Parent---render');
    const {carName} = this.state
    return (
      <div className="parent">
        <h3>我是Parent组件</h3>
        {this.state.stus}&nbsp;
        <span>我的车名字是:{carName}</span><br/>
        <button onClick={this.changeCar}>点我换车</button>
        <button onClick={this.addStu}>添加一个小刘</button>
        <Child carName="奥拓"/>
      </div>
    )
  }
}

class Child extends PureComponent {

  /* shouldComponentUpdate(nextProps,nextState){
    console.log(this.props,this.state); //目前的props和state
    console.log(nextProps,nextState); //接下要变化的目标props,目标state
    return !this.props.carName === nextProps.carName
  } */

  render() {
    console.log('Child---render');
    return (
      <div className="child">
        <h3>我是Child组件</h3>
        <span>我接到的车是:{this.props.carName}</span>
      </div>
    )
  }
}

render props

如何向组件内部动态传入带内容的结构(标签)?

类似于Vue中的插槽

Vue中:

使用slot技术, 也就是通过组件标签体传入结构

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}

错误边界

理解

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

特点

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

使用方式

getDerivedStateFromError配合componentDidCatch

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

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

示例

import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {

	state = {
		hasError:'' // 用于标识子组件是否产生错误
	}

	//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
	static getDerivedStateFromError(error){
		console.log('@@@',error);
		return {hasError:error}
	}

	componentDidCatch(){
		console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
	}

	render() {
		return (
			<div>
				<h2>我是Parent组件</h2>
				{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
			</div>
		)
	}
}

组件通信方式总结

组件间的关系

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式

  1. props: (1).children props (2).render props
  2. 消息订阅-发布: pubs-sub、event等等
  3. 集中式管理: redux、dva等等
  4. Context: 生产者-消费者模式

    比较好的搭配方式

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、Context(开发用的少,封装插件用的多)