这也算是我写的第二篇长篇笔记了

学习包含react18-+,后面代码部分多以react18方式书写

适合:初学 React

1. 安装使用

1.1 基本使用

<body>
  <div id="test"></div>
  <!-- 以下两个包的顺序固定 -->
  <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <!-- 用于解析 jsx 语法 -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!--type 必须是 text/babel -->
  <script type="text/babel">
    //1. jsx 语法
    const VDOM = <h1>hellore</h1>
    //2. 绑定虚拟 dom 到真实 dom
    ReactDOM.render(VDOM, document.getElementById('test'))
  </script>
</body>
  1. 再次使用 ReactDOM.render(VDOM, document.getElementById('test')) 会覆盖前面绑定的内容
  2. react.development.js 和 react-dom.development.js 顺序不能颠倒

1.2 不用 jsx 也能创建虚拟 DOM

const VDOM = React.creatElment('span',{id:'test'},'hello')
ReactDOM.render(VDOM, document.getElementById('test'))

1.3 JSX 语法规则

  1. 定义虚拟 DOM 不要引号
  2. 混入 JS 表达式 要用 { }
  3. 样式类名不能用 class 要用 className (为了防止于类关键字冲突)
  4. 内联样式格式 style={{ key: value }},多个单词组成的 key 要写成 小驼峰形式
  5. 只有一个根标签
  6. 标签必须闭合
  7. 标签首字母
    1. 如果首字母小写,会转换为 HTML 标签,如果不存在则报错
    2. 如果首字母大写,会渲染对应 React 组件,如果不存在则报错
const data = 'hello'
		//jsx 语法
		const VDOM = (
			<div>
				<h1 className='title'>
					<span style={{ color: 'red' }}>{data}</span>
				</h1>
				<input type="text" />
			</div>

		)
//绑定虚拟 dom 到真实 dom
ReactDOM.render(VDOM, document.getElementById('test'))

1.4 JSX 语法注意事项

  1. 当在虚拟 DOM 中插入可迭代对象时,React 会自动遍历
const arr = [1, 2, 3, 4]
		//jsx 语法
		const VDOM = (
			<div>
				<h1>{arr}</h1>
			</div>
		)
ReactDOM.render(VDOM, document.getElementById('App'))

  1. 如果需要额外处理,用 .map
const arr = [1, 2, 3, 4]
//jsx 语法
const VDOM = (
	<div>
		<h1>
			{
				arr.map((item, index) => {
					return <li key={index}>{item}</li>
				})
			}
		</h1>
	</div>
)
//绑定虚拟 dom 到真实 dom
ReactDOM.render(VDOM, document.getElementById('App'))


2. 组件

2.1 函数式组件

  1. 绑定得用 形式
  2. 类中的 this 指向 undefined
function Mycomponent () {
	console.log(this) //undefined 因为babel启用了严格模式
	return <h1>函数式组件</h1>
}
//绑定虚拟 dom 到真实 dom
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

函数组件使用 state 和 ref 要借助了两个钩子 useState 与 useRef

import React, { useState, useRef } from 'react'
export default function Count () {
	const [count, setCount] = useState(0)
	const slt = useRef()
	const add = () => {
		setCount(count + slt.current.value * 1)
	}
	return (
		<div>
			<h1>Count: {count}</h1>
			<select ref={slt}>
				<option value="1">1</option>
			</select>
			<button onClick={add}>add</button>
		</div>
	)
}

2.2 类式组件

  1. 绑定得用 形式
  2. 类中的 this 指向组件实例对象
  3. 固定写法如下
class Mycomponent extends React.Component {
	render () {
		return (
			<div>
				<span>类组件</span>
			</div>
		)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.3 state

class Mycomponent extends React.Component {
	constructor(props) {
		super(props)
		this.state = { isHot: true } //初始化 state
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
}
//绑定虚拟 dom 到真实 dom
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.3.1 事件绑定修改state

React 推荐用内联方式绑定

  1. 第一种写法(开发中肯定不能这么写)
class Mycomponent extends React.Component {
	constructor(props) {
		super(props)
		this.state = { isHot: true }
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span onClick={demo}>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
}
function demo () {
	console.log('点击');
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))
  1. 第二种写法:我们希望将 demo 方法拿到 Mycomponent 类里面去,但是 函数中的 this 指向会产生问题
class Mycomponent extends React.Component {
	constructor(props) {
		super(props)
		this.state = { isHot: 1 }
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span onClick={this.demo}>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
	demo(){
		console.log(this) //undefined
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

Mycomponent 是一个类,onClick 在绑定事件时候并不是用类的实例对象调用 demo ,所以 this 会指向 undefined(因为类中默认开启了严格模式),他的调用方式类似

let x = new Mycomponent()
let y = x.demo
y()//此时this指向会有问题

解决办法: constructor 函数中添加 this.demo = this.demo.bind(this)

this.demo.bind(this) 会返回一个 this 指向为实例对象的函数

class Mycomponent extends React.Component {
	constructor(props) {
		super(props)
		this.state = { isHot: true }
    //this.demo方法会在原型上找到,并返回一个内部 this 指向组件实例对象的方法,
    //并赋值给组件实例对象的新属性demo
		this.demo = this.demo.bind(this) 
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span onClick={this.demo}>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
  //demo会放在Mycomponent的原型对象上供实例使用
	demo(){
		console.log(this) //
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.3.2 setState 修改 State

  1. setState() 修改 state 是合并 state ,不是覆盖
class Mycomponent extends React.Component {
	constructor(props) {
		super(props)
		this.state = { isHot: true }
		this.demo = this.change.bind(this)
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span onClick={this.demo}>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
	change () {
		//这样直接修改,state确实变了,但是页面不会触发更新
		//this.state.isHot = !this.state.isHot
		//要使用内置的setState()函数来修改state
		this.setState({
			isHot: !this.state.isHot
		})
		console.log(this.state.isHot)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))
  1. setState 的完整用法
import React, { Component } from 'react'

export default class Demo extends Component {
	state = { count: 0 }
	/* 第一种对象式,本质是函数式的语法糖 */
	add = () => {
		/* 
				setState() 确实是同步方法,但是 react 的更新状态是异步的 
				第二个参数是个回调,是当 react 更新状态后调用的
		*/
		this.setState({ count: this.state.count + 1 }, () => console.log(this.state.count)) // 1
		console.log(this.state.count) // 0
	}
	/* 第一种函数式 */
	add2 = () => {
		/* 第一个回调能接收到 state 和 props ,第二个回调是当 react 更新状态后调用的*/
		this.setState((state, props) => { }, () => { })
	}
	render () {
		return (
			<div>
				<h1>count: {this.state.count}</h1>
				<button onClick={this.add}>ADD</button>
			</div>
		)
	}
}

这里便和函数式组件用 state 有区别

比如 const [state, setState] = useState({ count: 0 })

这里 setState 是同步的,更新状态也是同步的

2.3.3 state 简写方式 (开发写法)

由于之前的那样写是因为在类中直接定义方法,方法会被放在类的原型上,所以在绑定事件触发时候, this 指向会是 undefined 。所以需要在构造函数中this.demo = this.change.bind(this) 之类的代码来给组件实例对象属性上赋一个拥有正确 this 的函数。

那我们何不直接给组件实例对象添加呢?所有有以下写法。

那么当触发事件时候就是由实例来触发了

class Mycomponent extends React.Component {
	//state 初始化也直接写
	state = {
		isHot: true
	}
	render () {
		const { isHot } = this.state
		return (
			<div>
				<span onClick={this.change}>天气{isHot ? '炎热' : '寒冷'}</span>
			</div>
		)
	}
	//必须用箭头函数,不然 this 指向会出错
	change = () => {
		this.setState({
			isHot: !this.state.isHot
		})
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

this.setState({ }) 并不会把状态清空,而是相当于 this.setState(state)

2.4 props

2.4.1 props 介绍

  1. 用于外部数据传输至组件内
  2. 只读不可修改
  3. 组件传递的props,构造函数都能接到
constructor(p) {
  super(p)
	console.log(p === this.props); // ture
}

2.4.2 props 的基本使用

class Mycomponent extends React.Component {
	render () {
		const { name, age, address } = this.props
		return (
			<ul>
				<li>{name}</li>
				<li>{age}</li>
				<li>{address}</li>
			</ul>
		)
	}
}
ReactDOM.render(<Mycomponent name='Tom' age='29' address='xxx' />, document.getElementById('App'))
ReactDOM.render(<Mycomponent name='Jack' age='19' address='xxx' />, document.getElementById('App2'))
ReactDOM.render(<Mycomponent name='Xz' age='9' address='xxx' />, document.getElementById('App3'))

2.4.3 批量传递 props

class Mycomponent extends React.Component {
	render () {
		const { name, age, address } = this.props
		return (
			<ul>
				<li>{name}</li>
				<li>{age}</li>
				<li>{address}</li>
			</ul>
		)
	}
}
//--
const obj = {
	name: 'Tom',
	age: 18,
	address: 'xxxxx'
}
//--
ReactDOM.render(<Mycomponent {...obj} />, document.getElementById('App'))

注意 ❗❗❗

展开运算符 ... 并不能展开对象,此处组件中的 {...obj} 是 React 提供的语法糖,{ } 符号在这里实际只是分隔符的作用,所以本质上 React 在标签中...obj合法化。

  1. 但是在原生 JS 中 {...obj} 是对对象的浅拷贝 ,详情 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax
  2. 可见 ...obj 在 JSX 中也只能在组件绑定的标签中用
//在JSX中
let obj = {}
console.log(...obj) //也会报错

2.4.4 约束 props 类型

  1. 引入新的 js 文件 prop-type.js,引入后全局会多一个 PropTypes 对象用于表示限制类型
  2. 在传入props 时候采用 { } 语法,如果限定为 number 类型,传入字符串数字也会报错
  3. 限定函数类型要用 PropTypes.func (为了防止与关键词 function 冲突)
  4. .isRequired 代表是否必须
<!-- 引入约束 props 的类型包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/prop-types/15.8.1/prop-types.js"></script>
<div id="App"></div>
<script type="text/babel">
	class Mycomponent extends React.Component {
		render () {
			const { name, age, address } = this.props
			return (
				<ul>
					<li>{name}</li>
					<li>{age + 1}</li>
					<li>{address}</li>
				</ul>
			)
		}
	}
	//isRequired 代表是否必须
	Mycomponent.propTypes = {
		name: PropTypes.string,
		age: PropTypes.number.isRequired,
    speak: PropTypes.func
	}
	//定义默认属性
	Mycomponent.defaultProps = {
		address: 'defaultAddress'
	}
	function speak () {/* code */ }
	// 传其他类型用{}语法
	ReactDOM.render(<Mycomponent name={'Xz'} age={9} speak={speak} />, document.getElementById('App'))
</script>

组件的属性叫 proptypes,全局的约束对象是 PropTypes;勿混淆

2.4.5 props 简写

<!-- 引入约束 props 的类型包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/prop-types/15.8.1/prop-types.js"></script>
<div id="App"></div>
<script type="text/babel">
	class Mycomponent extends React.Component {
		//isRequired 代表是否必须
		static propTypes = {
			name: PropTypes.string,
			age: PropTypes.number.isRequired
		}
		//定义默认属性
		static defaultProps = {
			address: 'defaultAddress'
		}
		render () {
			const { name, age, address } = this.props
			return (
				<ul>
					<li>{name}</li>
					<li>{age + 1}</li>
					<li>{address}</li>
				</ul>
			)
		}
	}
	function speak () {/* code */ }
	// 传其他类型用{}语法
	ReactDOM.render(<Mycomponent name={'Xz'} age={9} speak={speak} />, document.getElementById('App'))
</script>

2.4.6 函数式组件用 props

function Mycomponent (props) {
	const { name, age, address } = props
	return (
		<ul>
			<li>{name}</li>
			<li>{age}</li>
			<li>{address}</li>
		</ul>
	)
}
Mycomponent.propTypes = {
	name: PropTypes.string,
	age: PropTypes.number.isRequired
}
Mycomponent.defaultProps = {
	address: 'defaultAddress'
}
ReactDOM.render(<Mycomponent name={'Xz'} age={9} />, document.getElementById('App'))

它用不了 refs 和 state

2.5 refs

2.5.1 描述

2.5.2 字符串形式 ref

官方已经不推荐使用❗

class Mycomponent extends React.Component {
	render () {
		return (
			<div>
				<input type="text" ref='input1' />
				<input type="button" value='输出' ref='btn1' onClick={this.tip} />
				<input type="text" ref='input2' placeholder='失焦输出' onBlur={this.log} />
			</div>
		)
	}
	tip = () => {
		const { input1 } = this.refs
		input1.value = 'ok'
	}
	log = () => {
		console.log('blur')
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.5.3 回调式 ref

  1. 内联写法

这样写在页面触发更新时会触发两次,第一次传入的 currentNode 为 null,第二次为当前节点

因为页面触发更新,render 会重新执行,但是此时的 this.input1 任然指向上一次的当前节点,所以为了严谨型 React 会先传个 null 将其执行一次将 this.input1 指向清空。(后面这一句为自己理解,正确性待商榷)

class Mycomponent extends React.Component {
  render () {
    return (
      <div>
      <input type="text" ref={currentNode => this.input1 = currentNode} />
      <input type="button" value='输出' onClick={this.tip} />
  <input type="text" ref={currentNode => this.inpute2 = currentNode} placeholder='失焦输出' onBlur={this.log} />
  </div>
)
}
	tip = () => {
	  const { input1 } = this
	  input1.value = 'ok'
	}
	log = () => {
	  console.log('blur')
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))
  1. class 绑定函数写法

不会执行多余次数

class Mycomponent extends React.Component {
	state = {
		size: true
	}
	render () {
		const { size } = this.state
		return (
			<div>
				<h1>-----{size ? '大' : '小'}-----</h1>
				<input type="text" ref={ this.saveInput } />
				<input type="button" value='改' onClick={this.change} />
			</div>
		)
	}
	saveInput = (currentNode) => {
		this.input1 = currentNode
	}
	change = () => {
		this.setState({ size: !this.state.size })
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

两种方法都行,执行两次没影响

官网解释https://react.docschina.org/docs/refs-and-the-dom.html

2.5.4 createRef (官方推荐)

class Mycomponent extends React.Component {
	//
	ref1 = React.createRef()
	ref2 = React.createRef()
	render () {
		return (
			<div>
				<input type="text" ref={this.ref1} />
				<input type="button" value='LOG' onClick={this.log} />
				<input type="text" ref={this.ref2} onBlur={this.blur} />
			</div>
		)
	}
	//
	log = () => {
		console.log(this.ref1.current.value)
	}
	blur = () => {
		console.log(this.ref2.current.value)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.5.5 事件处理

  1. React 的 onXxxx 事件是自己封装的,为了更好的兼容性
  2. React 中的事件处理是通过事件委托的形式委托给最外层元素,为了高效
  3. 不要过度使用 ref ,如下情况就可以少用 ref
class Mycomponent extends React.Component {
	//
	ref1 = React.createRef()
	render () {
		return (
			<div>
				<input type="text" ref={this.ref1} />
				<input type="button" value='LOG' onClick={this.log} />
				<input type="text" onBlur={this.blur} />
			</div>
		)
	}
	log = () => {
		console.log(this.ref1.current.value)
	}
	//
	blur = (e) => {
		console.log(e.target.value)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

2.6 受控 / 非受控组件

  1. 非受控组件:现用现取
  2. 受控组件:改变状态,例如给 input 绑定 onChange 事件来实时改变 state (亦如 Vue 的双向绑定)

2.7 函数柯里化

一个函数接受一个函数作为参数或者返回一个函数,那这个函数就叫高阶函数

函数柯里化即可这样 fun(1)(2)(3) 按层级调用

class Mycomponent extends React.Component {
	state = {
		username: '',
		password: ''
	}
	saveFormData = (dataName) => {
		return (e) => {
			this.setState({[dataName]: e.target.value})
		}
	}
	handleSubmit = (e) => {
		e.target.preventDefault = false //取消默认事件
	}
	render () {
		return (
			<form onSubmit={this.handleSubmit}>
				<input type="text" name='username' onChange={this.saveFormData('username')} />
				<input type="text" name='password' onChange={this.saveFormData('password')} />
				<input type="submit" value='提交' />
			</form>
		)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

onChange 执行的是回调函数

上面这种双向绑定的实现也可以不用函数柯里化

class Mycomponent extends React.Component {
	state = {
		username: '',
		password: ''
	}
  //
	saveFormData = (dataName, e) => {
		this.setState({ [dataName]: e.target.value })
	}
	handleSubmit = (e) => {
		e.target.preventDefault = false
	}
	render () {
		return (
			<form onSubmit={this.handleSubmit}>
				<input type="text" name='username' onChange={e => this.saveFormData('username', e)} />
				<input type="text" name='password' onChange={e => this.saveFormData('password', e)} />
				<input type="submit" value='提交' />
			</form>
		)
	}
}
ReactDOM.render(<Mycomponent />, document.getElementById('App'))

3. 生命周期

画板

4. 遍历时必须给 key 值的原因

4.1 过程

由于diff 算法是通过 key 来更新 DOM 的,过程如下

  1. 新的数据产生,React 产生新的虚拟 DOM
  2. 通过 key 与真实 DOM 进行比较 (通过 Diff 算法)
    1. key 相同:比较内容
      1. 内容相同:复用原来的真实 DOM
      2. 内容不同:生成新的真实 DOM 替换掉真实 DOM
    2. 不存在此 key:生成新的真实 DOM 渲染至页面

4.2 为何不推荐 index 作为 key

  1. key 重复
  2. 如果有输入类 DOM ,可能会产生错误效果
  3. 在列表前添加数据会造成后续数据不复用原来真实 DOM,效率低下
文本链接

如果上述 DOM 结构中文本发生变化,链接没有变化,那也只有文本更新,a 节点并不会重新渲染

5. 脚手架

5.1 安装使用

  1. 全局安装脚手架 npm i -g create-react-app
  2. 在指定目录下创建 react 项目 create-react-app 项目名
  3. 启动 npm start 或者 yarn start

5.2 目录讲解

有很多我们不熟悉的文件,但是这些并不是必需的,我们只需要将其精简为

npm eject 暴露所有核心配置文件(不可逆)

5.3 基本写法

入口文件 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

class ROOT extends React.Component {
  render () {
    return (
      <React.StrictMode>
        <App />
      </React.StrictMode>
    )
  }
}
/* 
	*React18 已经不在建议采用这种方式渲染至节点了
*/
ReactDOM.render(<ROOT />, window.root)

总组件文件 App.jsx

import Hello from "./components/Hello";
function App () {
  return (
    <div className="App">
      <Hello></Hello>
    </div>
  );
}

export default App;

子组件 Hello.jsx

import React,{Component} from "react"
import './index.css'
export default class Hello extends Component{
  render(){
    return <div>hello world</div>
  }
}

5.4 样式模块化

当多个组件都引入自己的 index.css 时候,即使他们不在一个文件夹,也会类名冲突(除非所有类名不同)

当我们在 vue 中时候 只需要在 vue 组件中的 style 标签加上一个 scoped 即可,可是 React 没这么直观

  1. 方法一:将 index.css 改名为 index.module.css 交由打包工具处理;然后
import React,{Component} from "react"
import style from './index.module.css'
export default class Hello extends Component{
	render(){
		return <div className={style.类名}>hello world</div>
	}
}
  1. 方法二:用 less 嵌套,使得各个组件中的 index.css 类名不一致
.hello{
  .类名{}
}

当然也可以头铁把所有类名设置得不一样

6. TodoList案例(父子通信)

具体案例代码https://github.com/xzboss/2023-study/tree/master/React/ReactCli/react_hello

6.1 父传子 用 props

父组件

<MyComponent msg={this.state.data}></MyComponent>

子组件

this.props.msg

6.2 子传父 用绑定事件方法

父组件

emit = (option)=>{}
render(){
  return <MyComponent fun={this.emit}></MyComponent>
}

子组件

emit = ()=>{
  this.props.fun(value)
}
render(){
  return <div onClick={this.emit}></div>
}

子组件触发时调用父组件函数,this 指向为父组件原理 在问题文档查找

6.3 关于 checked 与 defaultChecked 属性

6.3.1 checked

在 react 中需要搭配 onChange 使用,不然点不动

构成受控组件

6.3.2 defaultChecked

初始渲染一次默认值,随后修改不起作用

构成非受控组件

7. 配置代理

7.1 在 package.json 中配置(只能配置一个)

首先 react 项目开启在 3000 端口

package.json

{
  "proxy": "http://localhost:5000"
}

server.js

const express = require('express')
const app = express()
app.get('/student', (req, res) => {
	res.send('')
})
app.listen(5000, err => console.log("服务启动成功localhost:5000"))

使用

axios.get("http://localhost:3000/student").then(res => {//code}, err=>{})

7.2 采用配置文件 setupProxy.js (灵活多变)

在 src 下新建 setupProxy.js 文件

const {createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
	app.use(
		createProxyMiddleware('/A', { // 触发代理的路径前缀
			target: 'http://localhost:5000',//目标地址
      //是否隐藏自己真实地址(隐藏后后端的Host请求头里就不会是真实请求者的地址了)
			changeOrigin: true,
			pathRewrite: { '^/A': '' } //重写请求地址
		}),
		createProxyMiddleware('/B', {
			target: 'http://localhost:5001',
			changeOrigin: true,
			pathRewrite: { '^/B': '' }
		})
	)
}

server.js

const express = require('express')
const app = express()
app.use((request, response, next) => {
	console.log('Someone has requested the server')
	console.log('The requestor is', request.get('Host'))
	next()
})
app.get('/student', (req, res) => {
	res.send('')
})
app.listen(5000, (err) => { console.log("服务启动成功localhost:5000") })

使用

axios.get("http://localhost:3000/A/student").then(res => {//do something})

服务器控制台

但其实真实请求者为 3000,这就是 changeOrigin 的作用

如果向 3000 端口(react 启动端口)要资源,那么 public 文件夹中没有才会进行转发,因为 public 是默认的服务器根文件夹

8. GitHub 搜索案例 (pub-sub,axios,父子通信,fetch)

具体案例代码https://github.com/xzboss/2023-study/tree/master/React/ReactCli/react_hello

8.1 fetch 与 axios 区别

  1. axios 与 jQuery 都是对 XMLHttpRequest 对象实例的封装,但是 fetch 不是,他和 XMLHttpRequest 实例对象(也就是平时所说的 xhr)是平级的
  2. 取得数据方式不同
    1. axios 取数据
axios.get(url).then(res=>res.data)
2. fetch 的数据并不是返回对象身上的某个属性,它不能直接获得(关注分离的设计思想)
fetch(url).then(res=>res.json())
//得通过json方法返回得到,而json方法也并不是它本身具有的,而是它的原型对象上的
  1. 执行回调 then 不同
    1. axios 得到 404 状态会执行失败回调
axios(url).then(
	res => {},
	err => console.log('err')
)
2. fetch 只要联系到服务器就会执行成功回调,不管是否成功获取到数据
fetch(url).then(
	res => console.log('res'),
	err => {}
)

同样是 404 执行却不同

  1. async awat 与 fetch 一起使用
fun = async()=>{
  try {
		const res = await fetch(`/github/search/users?q=${value}`)
		const data = await res.json()
		console.log(data);
	} catch (error) {
		//..code
	}
}

9. 路由

接下来我就不用类组件的形式了

为了拥抱 hooks ,接下来全部采用函数式组件的写法

可以先划到后面看一下一些常用的 hooks

9.1 简述原理

通过改变地址栏,然后监听到地址栏变化,更新对应组件

9.2 基础使用

  • 现在默认已经是 @6 版本了
  • react 脚手架默认是没有这个包的

App.jsx

import React from 'react'
import Header from './component/Header'
import { Routes, Route, NavLink } from 'react-router-dom'
import About from './page/About'
import Home from './page/Home'
export default function App () {
  return (
    <div className="container">
      <div className="row">
        <Header />
      </div>
      <hr />
      <div className="row">
        <div className='list-group col-4'>
          <NavLink className='list-group-item' to='/about'>to About</NavLink>
          <NavLink className='list-group-item' to='/home'>to Home</NavLink>
        </div>
        <div className='col-8'>
          <Routes>
            <Route path="/about" element={<About />} />
            <Route path="/home" element={<Home />} />
          </Routes>
        </div>
      </div>
    </div>
  )
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
class ROOT extends React.Component {
  render () {
    return (
      <React.StrictMode>
        <App />
      </React.StrictMode>
    )
  }
}
ReactDOM.render(
  <BrowserRouter>
    <ROOT />
  </BrowserRouter>
  , window.root)

效果

注意事项

  1. 有路由组件的组件必须被 BrowerRoute 组件或者 HashRoute 组件所包裹,但是路由组件经常使用,所以索性就把 Root 组件全部包裹起来
  2. 路由注册组件 必须被包裹在 Routes 组件里 (用处就是当匹配到正确路由后便不再继续往下匹配,但是不写会出错,从以前的 Switch 组件包裹变成必须 Routes 组件包裹)

9.3 重定向

<Routes>
	<Route path="/about" element={<About />} />
  <Route path="/home" element={<Home />} />
  <Route path='/' element={<Navigate to="/about" />} />
</Routes>
<div>
	<h1>About</h1>
	{sum > 5 ? <Navigate to='/home' /> : <h3>{sum}</h3>}
	<button onClick={add}>+1</button>
</div>

Navigate 组件只要一渲染便会引起地址栏改变

9.4 小知识

  1. Link 组件默认是 push 操作,改为 replace :<Link replace>xxx</Link>
  2. 路由匹配默认不区分大小写
<Link to='/aBOUt'>to About</Link>
<Routes>
  <Route path="/about" element={<About />} />
</Routes>

这样也能匹配 ,若想区分大小写 <Route path="/about" caseSensitive element={<About />} />

  1. 解决样式丢失问题
  • 在 public/index.html 中引入资源:例如 <link rel="stylesheet" href="./boot.min.css">,刚开始能正确引用,但是当路由切换后 比如现在路由切换到 8080/home/user ,这时刷新页面,那请求样式资源路径将变成 8080/home/boot.min.css,自然是错误的
  • 解决办法:
    1. <link rel="stylesheet" href="/boot.min.css">
    2. <link rel="stylesheet" href="%PUBLIC_URL%/boot.min.css">

9.5.1 其实也就是当选中时 react 给他加类名

const changeClassName = ({ isActive }) => {
  return 'list-group-item ' + (isActive ? 'active' : '')
}
...
<NavLink className={changeClassName} to='/about'>to About</NavLink>

默认传参是一个对象,对象身上有个属性 isActive 表示是否选中

父组件 NavLink 添加 end 属性 那么子组件被选手中便会取消父组件被选中效果

MyNavLink

import React from 'react'
import { NavLink } from 'react-router-dom'
export default function MyNavLink (props) {
	console.log(props)// prop有一个children属性带着标签内容
	const changeClassName = ({ isActive }) => {
		return 'list-group-item ' + (isActive ? 'active' : '')
	}
	return (
		<NavLink className={changeClassName} {...props} />
	)
}

使用 to Home 会在 props.children 被接收到,NavLink 组件指定了 children 属性,那便就相当于写了 <NavLink>{props.children}</NavLink>

<NavLink className={changeClassName} to='/about'>to About</NavLink>
<NavLink className={changeClassName} to='/home'>to Home</NavLink>
<MyNavLink to='/home'>to Home</MyNavLink>

9.6 路由表 useRoutes()

src/routes/index.js

import { Navigate } from "react-router-dom"
import About from "../page/About"
import Home from "../page/Home"
const routes = [
  {
    path: '/about',
    element: <About />
  },
  {
    path: '/home',
    element: <Home />
  },
  {
    path: '/',
    element: <Navigate to='/about' />
  }
]
export default routes

App.jsx

import React from 'react'
import { NavLink, useRoutes } from 'react-router-dom'
import routes from './routes'
export default function App () {
  //根据路由表生成对应路由规则
  const elements = useRoutes(routes)
  return (
    <div className="container">
      <div className='list-group col-4'>
        <NavLink className={changeClassName} to='/about'>to About</NavLink>
        <NavLink className={changeClassName} to='/home'>to Home</NavLink>
      </div>
      <div className='col-8'>
        {/*注册路由*/}
        {elements}
      </div>
    </div>
  )
}

9.7 嵌套路由

routes/index.js

import Home from "../page/Home"
import News from "../page/Home/News"
import Message from "../page/Home/Message"
const routes = [
	{
		path: '/home',
		element: <Home />,
		children: [
			{
				path: 'news',
				element: <News />
			},
			{
				path: 'message',
				element: <Message />
			}
		]
	},
]
export default routes

Home.jsx

import React from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function Home () {
	return (
		<div>
			<h2>home内容</h2>
			<div className='container border text-center'>
				<div className="row">
					<div className="col-2 list-group-item">
						<NavLink to='news'>News</NavLink>
					</div>
					<div className="col-2">
						<NavLink to='message'>Message</NavLink>
					</div>
				</div>
				<div className="row">
          {/*路由渲染位置*/}
					<Outlet />
				</div>
			</div>
		</div>
	)
}
  1. Outlet 组件用于指定组件渲染位置 (区别于{elements},Outlet 用于嵌套,类似于 Vue 的 RouterView)
  2. @6 版本里子组件路径可以不用加父组件路径了
    1. path: 'news':react 会自动加上父组件路径
    2. path: '/news':不能这样写,不然路径会变成...8080/news
    3. path: '/home/news':也是正确

不用路由表的嵌套(一般不用)

<Routes>
  <Route to='/home'>
    <Route to='news'></Route>
  </Route>
</Routes>

9.8 路由传参

在 @5 版本,路由传参通过类组件实现,组件会收到 3 个 props 属性,分别是 location,match,history

通过 params 传递参数在 props.match.params 中以对象方式存在

通过 query 传递参数在 props.location.search 中以字符串方式存在

通过 state 传递参数在 props.location.state 中以什么存在忘了,用时自己查

但是 @6 版本不行咯,为了拥抱 Hooks,直接就砍掉了

9.8.1 params 传参

routes/index.js

{
  path: 'news',
  element: <News />,
  children: [
    {
      path: 'detail/:id/:name/:content',
      element:<Detail/>
    }
  ]
}

News.jsx (传递参数组件)

import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
export default function News () {
	const [data] = useState([
		{ id: '001', name: '消息一', content: '消息一的内容' },
		{ id: '002', name: '消息二', content: '消息二的内容' },
		{ id: '003', name: '消息三', content: '消息三的内容' }
	])
	return (
		<div>
			<div>
				{data.map(m => {
					return (
						<div key={m.id}>
							<Link to={`detail/${m.id}/${m.name}/${m.content}`}>{m.name}</Link>
						</div>
					)
				})}
			</div>
			<hr />
			<div>
				<Outlet />
			</div>
		</div>
	)
}

Detail.jsx (接收参数组件)

import React from 'react'
import { useParams } from 'react-router-dom'
export default function Detail () {
  // /useParams()返回对象
	const {id,name,content} = useParams()
	return (
		<div>
			<ul>
			<li>{id}</li>
			<li>{name}</li>
			<li>{content}</li>
			</ul>
		</div>
	)
}

接收参数还可以用 useMatch() ,但用的不多

const x = useMatch()

数据在 x.params 中

9.8.2 search 传参

routes/index.js

{
  path: 'message',
  element: <Message />,
  children: [
    {
      path: 'detail',//无需占位
      element:<Detail/>
    }
  ]
}

Message.jsx (传递参数组件)

<Link to={`detail?id=${m.id}&name=${m.name}&content=${m.content}`}>{m.name}</Link>

Detail.jsx (接收参数组件)

import React from 'react'
import { useSearchParams, useLocation } from 'react-router-dom'
export default function Detail () {
  //useSearchParams() 返回数组
	const [search,setSearch] = useSearchParams()
	const id = search.get('id')
	const name = search.get('name')
	const content = search.get('content')
  //useLocation() 获得search参数(不常用,一般用来获取state参数)
  console.log(useLocation())
  //setSearch() 可修改带来的参数 同时改变地址栏
  //setSearch('id=x&name=x&content=x')
	return (
		<div>
			<ul>
			<li>{id}</li>
			<li>{name}</li>
			<li>{content}</li>
			</ul>
		</div>
	)
}

useLocation() 返回值

9.8.3 state 传参

routes/index.js

{
  path: 'message',
  element: <Message />,
  children: [
    {
      path: 'detail',//无需占位
      element:<Detail/>
    }
  ]
}

Message.jsx (传递参数组件)

<Link to='detail' state={{ ...m }}>{m.name}</Link>

Detail.jsx (接收参数组件)

import React from 'react'
import { useLocation } from 'react-router-dom'
export default function Detail () {
	const { state: { id, name, content } } = useLocation()
	return (
		<div>
			<ul>
				<li>{id}</li>
				<li>{name}</li>
				<li>{content}</li>
			</ul>
		</div>
	)
}

9.9 编程式路由

9.9.1 基础使用 useNavigate

import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function Header () {
	const navigate = useNavigate()
	const toAbout = () => {
		navigate('/about', {
			replace: false,
			state: {
				msg: 'xxx'
			}
		})
	}
	return (
		<div className='col'>
			<h1 className='text-center'>Header</h1>
			<button type='button' className="btn btn-dark" onClick={toAbout}>to About</button>
			<button className="btn btn-dark" onClick={() => navigate(-1)}>back</button>
			<button className="btn btn-dark" onClick={() => navigate(1)}>forward</button>
		</div>
	)
}

@6 版本不需要用 withRouter 将一般组件变成路由组件了,只要使用 useNavigate() 就能作为路由组件

前进后退分别传参数 1,-1

9.9.2 useInRouterContext

只要被 BrowerRouter 组件所包裹的组件,此钩子返回 true 否则返回 false

9.9.3 useNavigationType

  1. 返回导航类型(用户是如何来到这个页面的)
  2. 返回值 POP(刷新页面),REPLACE,PUSH

9.9.4 useOutlet

  1. 返回当前组件渲染的嵌套组件
  2. 若还没渲染则返回 null

9.9.5 useResolvedPath

解析路径中的 path,search,hash 值

9.10 BrowserRouter 和 HashRouter 的 区别

https://juejin.cn/post/7030973301698592804

9.11 路由懒加载

fallback 指定未加载时组件

import React, { lazy, Suspense } from 'react'
import { Link, Route, Routes } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
export default function App () {
  return (
    <div>
      <Link to='/about'>about</Link>&nbsp;
      <Link to='/home'>home</Link>
      <div>
        <Suspense fallback={<h1>Loading...</h1>}>
          <Routes>
            <Route path='/about' element={<About />}></Route>
            <Route path='/home' element={<Home />}></Route>
          </Routes>
        </Suspense>
      </div>
    </div>
  )
}

10. Antd

10.1 基础使用

  1. 导入包 npm i antd
  2. App.jsx 引入样式 import '../node_modules/antd/dist/reset.css'
  3. 引入要用的组件 import {Button} from 'antd'

具体使用查看文档

10.2 按需引入

@5 版本采用 CSS-in-JS ,自带按需引入

@4 版本看文档

10.3 自定义主题

看文档

11. redux

注意与 react-redux 的区别

11.1基础使用

  1. redux/constant.js
/* 
	全局变量
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
  1. redux/count_reducer.js
/* 
	对状态进行操作
*/
import { INCREMENT, DECREMENT } from "./constant"
/* 初始状态 */
const initState = 0
export default function countReducer (preStore = initState, action) {
	const { type, data } = action
	switch (type) {
		case INCREMENT:
			return preStore + data
		case DECREMENT:
			return preStore - data
		default:
			return preStore
	}
}	
  1. redux/countAction.js
/* 
	创建action对象
*/
export const createIncrement = data => ({ type: 'increment', data })
export const createDecrement = data => ({ type: 'decrement', data })
/* 创建异步action对象 (其实就是异步里面调同步)*/
export const createAsyncIncrement = (data, time) => {
	/* dispatch如果接收到一个函数,那么store会调用它,并默认给他传递一个dispatch参数 */
	return (dispatch) => {
		setTimeout(() => {
      /* 派发同步 action */
			dispatch(createIncrement(data))
		}, time)
	}
}
  1. redux/store.js
/* applyMiddleware 使用中间件 */
import { createStore, applyMiddleware } from 'redux'
/* 
**安装包 redux-thunk
**按需要安装它,没有异步需求就不需要
*/
//引入可以使用异步 action 的中间件,有了它 dispatch 就可以接收一个函数
import thunk from 'redux-thunk'
import countReducer from './count_reducer'
export default createStore(countReducer, applyMiddleware(thunk))
  1. Count组件
import React, { useState, useEffect } from 'react'
import store from '../../redux/store'
import { createIncrement, createAsyncIncrement } from '../../redux/countAction'
export default function Count () {
	const [update, setUpdate] = useState(true)
	const increment = () => {
    /* 派发操作 */
		store.dispatch(createIncrement(1))
		setUpdate(!update)
	}
	useEffect(() => {
		/* 
		**只要store里的数据发生了变化,就会执行回调 
		**就和发布订阅一样
		*/
		store.subscribe(() => {
			setUpdate(!update)
		})
	})
	const add_async = () => {
		store.dispatch(createAsyncIncrement(2, 1000))
	}
	return (
		<div>
			<h1>Count: {store.getState()}</h1>
			<button onClick={increment}>increment</button>
			<button onClick={decrement}>decrement</button>
			<button onClick={add_odd}>add_odd</button>
			<button onClick={add_async}>add_async</button>
		</div>
	)
}

如有异步需求需要创建异步 action ,要用到中间件 redux-thunk


12. react-redux

12.1 基本使用

安装依赖包 react-redux

redux 文件夹衔接上文

  1. src/containers/Count/index.jsx 容器组件
import CountUI from "../../components/Count"
import { connect } from "react-redux"
import { createIncrement, createAsyncIncrement } from "../../redux/countAction"
//返回一个对象,对象的 key 映射为 UI组件的 props 的 key
//自动传入一个参数,即状态 store.getState()
function mapStateToProps (state) {
	return { count: state }
}
//返回一个对象,对象的 key 映射为 UI组件的 props 的 key
//自动传入一个参数,即派发函数 store.dispatch
function mapDispatchToProps (dispatch) {
	return {
		increment: (num) => {
			dispatch(createIncrement(num))
		},
		asyncIncrement: (num, time) => {
			dispatch(createAsyncIncrement(num, time))
		}
	}
}
//connect 接收两个参数
//第一个用来映射状态,第二个用来映射操作状态的方法
//connect 返回一个函数,用于连接 UI 组件(传递的参数即连接的UI组件)
//最后返回一个容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
  1. components/Count/index.jsx UI组件
import React, { } from 'react'
export default function Count (props) {
	const increment = () => {
		props.increment(1)
	}
	const decrement = () => { }
	const add_odd = () => { }
	const add_async = () => {
		props.asyncIncrement(1, 1000)
	}
	return (
		<div>
			<h1>Count: {props.count}</h1>
			<button onClick={increment}>increment</button>
			<button onClick={decrement}>decrement</button>
			<button onClick={add_odd}>add_odd</button>
			<button onClick={add_async}>add_async</button>
		</div>
	)
}
  1. App.jsx
import React, { useState, useEffect } from 'react'
import Count from './containers/Count'
import store from './redux/store'
export default function App () {
  const [state, setState] = useState(true)
  useEffect(() => {
    store.subscribe(() => {
      setState(!state)
    })
  })
  return (
    <div>
      <Count store={store} />
    </div>
  )
}

store 不是在容器组件中引入,而是通过 props 传入

12.2 简写 mapDispatchToProps

import CountUI from "../../components/Count"
import { connect } from "react-redux"
import { createIncrement, createAsyncIncrement } from "../../redux/countAction"
function mapStateToProps (state) {
	return { count: state }
}
// react-redux 会自动派发
const mapDispatchToProps = {
	increment: createIncrement,
	asyncIncrement: createAsyncIncrement
}
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)

12.3 优化 store

  1. 不再需要监听 store

用了 react-redux 就不用再用 store.subcribe(()=>{}) 监听 store 数据的改变了

他会在 connect 中自动监测数据改变从而更新组件

  1. Privder 组件

处于 Provider 组件内的容器组件会自动传入 store

App.jsx

import React, { } from 'react'
import Count from './containers/Count'
import store from './redux/store'
import { Provider } from 'react-redux'
export default function App () {
  return (
    <div>
      <Provider store={store}>
        <Count />
      </Provider>
    </div>
  )
}

12.4 合并 UI 组件与容器组件

因为 UI 组件和容器组件一一对应,当需要和 store 打交道的组件多了,那么组件系统会很庞大

src/containers/Count.jsx 容器组件

import { connect } from "react-redux"
import { createIncrement, createAsyncIncrement } from "../../redux/countAction"
import React, { } from 'react'
//UI 组件
function Count (props) {
  const increment = () => {
    props.increment(1)
  }
  const decrement = () => { }
  const add_odd = () => { }
  const add_async = () => {
    props.asyncIncrement(1, 1000)
  }
  return (
    <div>
      <h1>Count: {props.count}</h1>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <button onClick={add_odd}>add_odd</button>
      <button onClick={add_async}>add_async</button>
    </div>
  )
}



/* 容器组件 */

//返回一个对象,对象的 key 映射为 UI组件的 props 的 key
//自动传入一个参数,即状态 store.getState()
function mapStateToProps (state) {
  return { count: state }
}
//返回一个对象,对象的 key 映射为 UI组件的 props 的 key
//自动传入一个参数,即派发函数 store.dispatch
// react-redux 会自动派发
const mapDispatchToProps = {
  increment: createIncrement,
  asyncIncrement: createAsyncIncrement
}
//connect 接收两个参数
//第一个用来映射状态,第二个用来映射操作状态的方法
//connect 返回一个函数,用于连接 UI 组件(传递的参数即连接的UI组件)
//最后返回一个容器组件
export default connect(mapStateToProps, mapDispatchToProps)(Count)

12.5 合并 reducer ,多个组件间用 react-redux 通信

目录结构变更

  1. store.js变更
/* combineReducers 合并所有reducer */
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
/* 前面的 store.getState('key') 就能通过 key 来取了*/
const allReducer = combineReducers({
	count: countReducer,
	persons: personReducer
})
export default createStore(allReducer, applyMiddleware(thunk))
  1. contant.js
/* 
	全局变量
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
  1. Count容器组件
import { connect } from "react-redux"
import { createIncrement, createAsyncIncrement } from "../../redux/actions/count"
import React, { } from 'react'
//
function Count (props) {
	const increment = () => {
		props.increment(1)
	}
	const decrement = () => { }
	const add_odd = () => { }
	const add_async = () => {
		props.asyncIncrement(1, 1000)
	}
	return (
		<div>
			<h1>Count组件</h1>
			<button onClick={increment}>increment</button>
			<button onClick={decrement}>decrement</button>
			<button onClick={add_odd}>add_odd</button>
			<button onClick={add_async}>add_async</button>
			<ul>
				person组件的内容
				<hr />
				{
					props.persons.map(({ id, name, age }) => {
						return <li key={id}>name: {name} ----- age: {age}</li>
					})
				}
			</ul>
		</div>
	)
}

// 取值方式改变
function mapStateToProps (state) {
	return { count: state.count, persons: state.persons }
}
const mapDispatchToProps = {
	increment: createIncrement,
	asyncIncrement: createAsyncIncrement
}

export default connect(mapStateToProps, mapDispatchToProps)(Count)
  1. Person 容器组件
import React, { useRef } from 'react'
import { connect } from 'react-redux'
import { nanoid } from 'nanoid'
import { createAddPerson } from '../../redux/actions/person'
//UI 组件
function Person (props) {
	const nameNode = useRef()
	const ageNode = useRef()
	const add = () => {
		const name = nameNode.current.value
		const age = ageNode.current.value
		props.add_person({ id: nanoid(), name, age })
	}
	return (
		<div>
			<h1>person组件</h1>
			<input type="text" placeholder='name' ref={nameNode} />
			<input type="text" placeholder='age' ref={ageNode} />
			<button onClick={add}>添加</button>
			<h4>Count组件的值:{props.count}</h4>
		</div>
	)
}
// 暴露容器组件
export default connect(
	state => ({ count: state.count }),
	{
		add_person: createAddPerson
	}
)(Person)
  1. actions/person.js
import { ADD_PERSON } from "../constant"
export const createAddPerson = personObj => ({ type: ADD_PERSON, data: personObj })
  1. reducers/person.js
import { ADD_PERSON } from "../constant"
const initState = [
	{
		id: '0-1',
		name: 'xz',
		age: 12
	},
	{
		id: '0-2',
		name: '李易峰',
		age: 34
	}
]
export default function personReducer (preState = initState, action) {
	const { type, data } = action
	switch (type) {
		case ADD_PERSON:
			return [data, ...preState]
		default:
			return preState
	}
}

效果

12.6 纯函数

  1. 相同的输入,永远得到相同的输出

这句话可不能理解为参数与返回值相同,这句话的意思是:一个函数,传递给他一个参数,不管你调用多少遍,他的输出永远都是一样的(这里指的是输入输出的值,引用不同值相同也算输入输出一样)

比如 reducer 函数,prestate 与 action 两个值永远不变的话,那么它的返回值永远不变

  1. 不对外部产生影响

这个便好理解,比如操作 dom,发起请求,改了全局变量...这些都是对外部产生影响,(这些应该再action中处理)

都符合这两个条件便是纯函数

例子,一个 reducer 函数

const initState = [{id: '0-1',name: 'xz',age: 12}]
export default function personReducer (preState = initState, action) {
	const { type, data } = action
	switch (type) {
		case 'demoAction':
			preState[0].age = data //这里修改了preState,由于preState是一个引用,所以这样就不是纯函数了
      /* 关于react-redux有时状态改变了却不响应的原因 */
			return preState //这里是返回了preState,但是react并不会更新,因为preState的引用是没改变的,任然指向原来的对象,react便是不认为数据发生了变化
			//return [...preState] //这样返回新的引用这样react-redux才认为状态更新了
		default:
			return preState
	}
}

react-redux 默认会进行一下浅比较,来决定是否更新页面

12.7 react-redux 开发者工具

  1. 浏览器安装插件 Redux DevTools
  2. 项目安装依赖包 redux-devtools-extension
  3. store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
/* 导入 redux-devtools-extension */
import { composeWithDevTools } from 'redux-devtools-extension'
/* 前面的 store.gteState('key') 就能通过 key 来取了*/
const allReducer = combineReducers({
	count: countReducer,
	persons: personReducer
})
/* 插件作为 composeWithDevTools 的参数 */
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))

12.8 简化

再redux/reducers下创建一个index.js文件用于合并reducer

index.js

/* 用于合并reducer */
import { combineReducers } from 'redux'
import countReducer from './count'
import personReducer from './person'
/* 前面的 store.gteState('key') 就能通过 key 来取了*/
export default combineReducers({
	count: countReducer,
	persons: personReducer
})

store.js

/* applyMiddleware 使用中间件 */
/* combineReducers 合并所有reducer */
import { createStore, applyMiddleware } from 'redux'
/* 
**安装包 redux-thunk
**按需要安装它,没有异步需求就不需要
*/
//引入可以使用异步 action 的中间件,有了它 dispatch 就可以接收一个函数
import thunk from 'redux-thunk'

/* 导入 redux-devtools-extension */
import { composeWithDevTools } from 'redux-devtools-extension'

import reducers from './reducers'

export default createStore(reducers, composeWithDevTools(applyMiddleware(thunk)))

13 打包

  1. 打包后 serve build 快速以 build 文件夹为根目录开启一台服务器
  2. 打包后的文件直接打开不能运行,因为直接打开是用的 file 协议,打包好的路径格式默认为 http 协议下的请求资源格式 (但是把 index.html 下的 / 开头资源路径改成 ./ 就能正常打开)

14 Hooks

以后都用这个喽,类组件少用啦

14.1 useState

import React, { useState } from 'react'

export default function App () {
  const [count, setCount] = useState(0)
  const add = () => {
    /* 第一种 */
    //setCount(count + 1)
    /* 第二种 */
    setCount(count => count + 1)
    console.log(count) // 1
  }
  return (
    <div>
      <h1>count: {count}</h1>
      <button onClick={add}>ADD</button>
    </div>
  )
}

与类组件的相比,useState 触发的更新是同步的,而类组件中 this.setState() 方法是同步的,但触发的更新是异步的

使用哪种方法依据:

  1. 改变依赖原状态用 setCount(coun => count + 1)
  2. 不依赖原状态用 setCount(newCount)

为什么 setCount() 后重新执行了 App() ,但是 const [count, setCount] = useState(0)这句代码不重新执行能,因为 react 底层做了处理,据说是单例模式,后续再去研究吧 ❓

14.2 useEffect

useEffect 回调执行时机

  1. 组件挂载时
  2. 监听的状态改变后

useEffect 组成

  1. 第一个回调用来做请求,定时器...等副作用功能
  2. 第一个回调的返回值作为组件卸载时的回调,用于关闭定时器,取消订阅...
  3. 第二个参数表示监听的状态集,如果不写表示监听所有状态
import React, { useState, useEffect } from 'react'
import { root } from './index'
export default function App () {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
    return () => {
      clearInterval(timer)
    }
  }, [])
  const unmount = () => {
    root.unmount()
  }
  return (
    <div>
      <h1>count: {count}</h1>
      <button onClick={unmount}>卸载组件</button>
    </div>
  )
}

14.3 useRef

import React, { useRef } from 'react'
import { root } from './index'
export default function App () {
  const node = useRef()
  const log = () => {
    console.log(node.current.value)
  }
  return (
    <div>
      <input type="text" ref={node} />
      <button onClick={log}>alert</button>
    </div>
  )
}

14.4 Fragment

import React, { Fragment } from 'react'
import { root } from './index'
export default function App () {
  return (
    <Fragment key={1} children={<h1>优先级 2 </h1>}>
      <h1>优先级 1 </h1>
    </Fragment>
  )
}

他与 <></> 有所区别,它可以传 key 值,当用到遍历时就很有用了,还有他可以指定 children 属性,如果 Fragment 标签内部没有写其他内容,那他的内容就会是 children

14.5 createContext 生产者-消费者模式

用于祖组件给孙孙孙...组件传值

import React, { Component, createContext } from 'react'
import './index.css'
//创建 context 对象
const MyContext = createContext()
// Provider 组件用来传递 ,Consumer用来接收 (类组件还有另一种方式接收)
const { Provider, Consumer } = MyContext
export default class A extends Component {
	render () {
		return (
			<div className='A'>
				<h1>A组件 传递的值是 100</h1>
				{/* 只能用 value 属性传值 */}
				<Provider value={100}>
					<B />
				</Provider>
			</div>
		)
	}
}
//B 组件
class B extends Component {
	render () {
		return (
			<div className='B'>
				<h2>B组件</h2>
				<C />
			</div>
		)
	}
}
/* 这个接收方式只适用于类组件 */
/* class C extends Component {
	// 必须用 static
	// 声名 C 属于这个上下文
	// 声名了之后才可以通过 this.context 接收
	static contextType = MyContext
	render () {
		return (
			<div className='C'>
				<h4>C组件 接收的值是:{this.context}</h4>
			</div>
		)
	}
} */
/* 这个接收方式适用于类组件与函数组件 */
function C () {
	return (
		<div className='C'>
			<h4>
				C组件 接收的值是:<Consumer>{value => value}</Consumer>
			</h4>
		</div>
	)
}
/* 函数组件还有一种接收方法 const value = useContext(MyContext) */

其实吧,这个一般用来封装插件,不用来开发写

react-redux 的 Provider 组件就是这个原理

14.6 useLayoutEffect

与 useEffect 的不同点

  • 执行时机不同。useLayoutEffect DOM 更新之后执行;useEffect render 渲染结束后执行。执行示例代码会发现 useLayoutEffect 永远比 useEffect 先执行,这是因为 DOM 更新之后,渲染才结束或者渲染还会结束

其他 hook 看官网吧

15 其他

15.1 PrueComponents 优化 类组件

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

/* 解决办法 二,继承 PureComponent */
/* PureComponent原理其实也是重写 shouldComponentUpdate ,但是他只在里面进行了浅比较
	也就是说用这个方法,就算状态改变了,但是传入的引用一样,那么不会调用 render
	比如 this.state(this.state)
	*/
export default class Demo extends PureComponent {
	state = { count: 0 }
	handle = () => {
		this.setState({ count: 0 })//状态并没有改变,但是却要调用 render
	}
	/* 解决办法 一,重写shouldComponentUpdate */
	/* shouldComponentUpdate (nextProps, nextState) {
		console.log(this.state, nextState)
		return nextState.count !== this.state.count
	} */
	render () {
		console.log('@@@')
		return (
			<div>
				count: {this.state.count}&nbsp;
				<button onClick={this.handle}>触发</button>
			</div>
		)	
	}
}
/* 这里插播一个小知识,this.setState() 执行顺序是 先触发 shouldComponentUpdate,并将新状态传给它,通过后再改变状态,再render*/

hooks 中的 useState() 出来的操作状态的方法,默认是做了 PureComponent 组件这样的处理的,也是浅比较,意味着也不能 setObj(obj)

15.2 render Props

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

export default class A extends Component {
	render () {
		return (
			<div className='A'>
				<h1>A组件</h1>
				<B render={(v) => <C count={v} />} />
			</div>
		)
	}
}
//B 组件
function B (props) {
	const [count, setCount] = useState(0)
	return (
		<div className='B'>
			<h2>B组件</h2>
			{props.render(count)}
		</div>
	)
}
//C 组件
function C (props) {
	return (
		<div className='C'>
			<h4>
				C组件: {props.count}
			</h4>
		</div>
	)
}

类似 vue 的插槽,比插槽底层,其实 vue 也是这样的原理

15.3 错误边界

高铁要开了,没时间写了

在生产环境有用,开发环境该报错还是报错

请使用 static getDerivedStateFromError() 渲染备用UI,使用componentDidCatch()打印错误信息

函数组件的错误边界百度吧,没空了

15.4 组件通信的方法

经验

1. 解构赋值连写

const that = {
	state: {
		data: 'msg'
	}
}
const { state: { data } } = that
//也可以解构赋值后重命名
const { state: { data: value } } = that
console.log(data, value) // msg msg

2. 三元连写

a?1:
  b?2:
  	c?3:4

3. 包管理器 npm 与 yarn 最好不要混用 容易造成包丢失

4. 箭头函数返回对象

const fun = () => ({count:0})

5. 取多个节点的值

const [{ value: name }, { value: password }] = [nameNnode.current, passwordNode.current]
console.log(name, password)

规范

1. target="_blank" 要与 rel="noreferrer" 一起使用

当你使用 target="_blank" 打开一个新的标签页时,新页面的 window 对象上有一个属性 opener ,它指向的是前一个页面的 window 对象,因此,后一个新打开的页面就可以控制前一个页面了,事情就是这么的可怕。而且不管它是否跨域了,都是可以的

加入了 rel="noopener noreferrer" 属性,就会 window.opener 会为 null

2. 脚手架警告

2.1 img 要加 alt 属性

Other

1. 插件

1.1 ES7+ React/Redux/React-Native snippetsv

  1. rcc :jsx 类组件模板
  2. rfc:jsx 函数组件模板

2. 包

2.1 nanoid 生成唯一标识

import nanoid from 'nanoid'

nanoid()

2.2 prop-types 限制 props

import PropTypes from 'prop-types'

往上翻

2.3 pub-sub 消息订阅与发布

import PubSub from 'pubsub-js'
//订阅
this.getGitHubID = PubSub.subscribe('getGitHub', (_, data) => {
      this.setState({ ...data })
})
//取消订阅,一般在ComponentWillUnmount中做
PubSub.unsubscribe(this.getGitHubID)
//发布
PubSub.publish('getGitHub', { })

其他详情请见文档

2.4 react-router-dom

见路由

2.5 querystring url参数和对象之间转换

<font style="color:rgb(37, 41, 51);">querystring.stringify(obj, [seq], [eq])</font> 对象转url串

<font style="color:rgb(37, 41, 51);">querystring.parse(str, [seq], [eq], [options])</font> url串转对象

seq 是分隔符,默认是 & ,eq 是分配符(键值中间符号),默认是 = ,options 是指是大长度,默认1000

2.6 redux-thunk

2.7 redux

2.8 react-redux