目标:了解什么是react
React官网
React 是一个用于构建用户界面(UI,对咱们前端来说,简单理解为:HTML 页面)的 JavaScript 库
React 起源于 Facebook 内部项目(News Feed,2011),后又用来架设 Instagram 的网站(2012),并于 2013 年 5 月开源(react介绍)
React 是最流行的前端开发框架之一,其他:Vue、Angular 等等(npm 下载趋势对比)
总结:
目标:能够说出react的三个特点
// JSX
<div className="app">
<h1>Hello React! 动态数据变化:{count}</h1>
</div>
npx create-react-app my-app
(my-app表示项目名称,可以修改)npm start
npm i -g create-react-app
create-react-app
直接创建 React 项目总结
create-react-app
是react官方提供一个脚手架工具,用于创建react项目npx create-react-app react-basic
命令可以快速创建一个react项目目标:掌握react的基本使用功能步骤
// 导入react和react-dom
import React from 'react'
import ReactDOM from 'react-dom'
// 创建元素
const title = React.createElement('h1', null, 'hello react')
// 渲染react元素
ReactDOM.render(title, document.getElementById('root'))
JSX
是JavaScript XML
的简写,表示了在Javascript代码中写XML(HTML)格式的代码
优势:声明式语法更加直观,与HTML结构相同,降低学习成本,提高开发效率。
JSX是react的核心内容
注意:JSX 不是标准的 JS 语法,是 JS 的语法扩展。脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法。
在 babel 的试一试中,可以通过切换 React Runtime 来查看:
import ReactDOM from 'react-dom'
// 创建元素
const title = <h1 title="哈哈"></h1>
// 渲染元素
ReactDOM.render(title, document.getElementById('root'))
<>>
(幽灵节点)或者
/>
结束class
===> className
for
===> htmlFor
()
包裹JSX,防止 JS 自动插入分号的 bugconsole.log( 表达式 )
const car = {
brand: '玛莎拉蒂'
}
const title = (
<h1>
汽车:{car.brand}
</h1>
)
const friends = ['张三', '李四']
const title = (
<h1>
汽车:{friends[1]}
</h1>
)
const age = 18
const title = (
<h1>
是否允许进入:{age >= 18 ? '是' : '否'}
</h1>
)
function sayHi() {
return '你好'
}
const title = <h1>姓名:{sayHi()}</h1>
const span = <span>我是一个span</span>
const title = <h1>JSX 做为表达式:{span}</h1>
{/* 这是jsx中的注释 */} 快键键 ctrl + /
目标:掌握在react中如何根据条件渲染结构
// 通过判断`if/else`控制
const isLoading = true
const load = (isLoading) => {
if (isLoading) {
return '数据加载中...'
} else {
return '数据加载完成,此处显示加载后的数据'
}
}
<div>
<h2>03-条件渲染</h2>
<h3>通过判断`if/else`控制</h3>
<p>{load(true)}</p>
<p>{load(false)}</p>
<h3>通过三元运算符控制</h3>
<p>{isLoading ? '数据加载中...' : '数据加载完成,此处显示加载后的数据'}</p>
<h3>逻辑与(&&)运算符</h3>
<p>{isLoading && '数据加载中...'}</p>
</div>
如果要渲染一组数据,应该使用数组的 map() 方法
作用:重复生成相同的 HTML 结构,比如,歌曲列表、商品列表等
注意:
key
属性 // 列表的渲染
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' }
]
const list = songs.map((item, k) => {
console.log(item, k + 1);
return <li>{item.id + '=>' + item.name}</li>
})
<ul>
{list}
</ul>
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' }
]
const dv = (
<div>
<ul>{songs.map(song => <li>{song.name}</li>)}</ul>
</div>
)
ReactDOM.render(
dv,
document.getElementById('root')
);
目标:掌握jsx中如何通过style和className控制元素的样式
const dv = (
<div style={ { color: 'red', backgroundColor: 'pink' } }>style样式</div>
)
// 导入样式
import './base.css'
const dv = <div className="title">style样式</div>
目标:能够知道 React 组件的意义
函数组件:使用 JS 的函数(或箭头函数)创建的组件
注意:
渲染结构:
// 渲染结构:
function Hello() {
return (
<div>这是我的第一个函数组件</div>
)
}
// 不渲染结构:
function Hello() {
return null
}
// 使用箭头函数创建组件:
const Hello = () => <div>这是我的第一个函数组件</div>
import { createRoot } from 'react-dom/client'
import { Component } from 'react'
// 测试渲染组件
const tpl = (
<div className="box">
<App></App>
<App />
<hr />
<List />
<hr />
<App2 />
</div>
)
// 老写法
// ReactDOM.render(tpl, document.getElementById('root'))
// 新写法
createRoot(document.getElementById('root')).render(tpl)
目标:使用ES6的class语法创建组件
// 导入 React
import React from 'react'
class Hello extends React.Component {
render() {
return <div>Hello Class Component!</div>
}
}
ReactDOM.render(<Hello />, root)
// 只导入 Component
import { Component } from 'react'
class Hello extends Component {
render() {
return <div>Hello Class Component!</div>
}
}
ReactDOM.render(<Hello />, root)
目标:能够将react组件提取到独立的js文件中
思考:项目中的组件多了之后,该如何组织这些组件呢?
实现步骤:
// index.js
import Hello from './Hello'
// 渲染导入的Hello组件
ReactDOM.render(<Hello />, root)
// Hello.js
import { Component } from 'react'
class Hello extends Component {
render() {
return <div>Hello Class Component!</div>
}
}
// 导出Hello组件
export default Hello
目标:理解函数组件和类组件的区别
静
态,不会发生变化),性能比较高目标:掌握react类组件中如何提供状态以及渲染状态
动
态)class Hello extends Component {
// 为组件提供状态
state = {
count: 0,
list: [],
isLoading: true
}
render() {
return (
// 通过 this.state 来访问类组件的状态
<div>计数器:{this.state.count}</div>
)
}
}
state
,即数据,是组件内部的私有数据,只能在组件内部使用this.state.xxx
来获取状态目标: 掌握react中如何注册事件
on+事件名 ={事件处理程序}
比如onClick={this.handleClick}
onMouseEnter
, onClick
import { Component } from 'react'
import '../../index.css'
class App extends Component{
// 为组件提供状态
handleClick(){
console.log("注册事件");
}
render(){
return(
<div className='box'>
<h2>3.4 React 事件处理</h2>
<h3>1. 事件处理-注册事件</h3>
<button onClick={this.handleClick}>注册事件</button>
</div>
)
}
}
export default App
目标:掌握注册事件时如何处理事件对象
import { Component } from 'react'
import '../../index.css'
class App extends Component{
// 为组件提供状态
handleClick(e){
console.log("注册事件",e.target);
// 事件对象
e.preventDefault()
}
render(){
return(
<div className='box'>
<h3>2. 事件处理-事件对象</h3>
<a href='https://element.eleme.io/#/zh-CN/component/message-box#quan-ju-fang-fa' onClick={this.handleClick}>事件对象</a>
</div>
)
}
}
export default App
目标:了解事件处理程序中this指向undefined且知道原因
undefined
undefined
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={this.handleClick}>点我</button>
</div>
)
}
}
目标: 掌握常见的this指向解决方案
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={() => this.handleClick()}>点我</button>
</div>
)
}
}
class App extends React.Component {
state = {
msg: 'hello react'
}
handleClick() {
console.log(this.state.msg)
}
render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>点我</button>
</div>
)
}
}
import React from 'react'
import ReactDOM from 'react-dom'
/*
从JSX中抽离事件处理程序
*/
class App extends React.Component {
state = {
count: 0
}
// 事件处理程序
onIncrement = () => {
console.log('事件处理程序中的this:', this)
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<App />, document.getElementById('root'))
// 为组件提供状态
state={
name:'张三疯',
age:19,
obj:{
name:'张三疯',
age:19,
}
}
<p>姓名:{this.state.name}<br/>年龄:{this.state.age}<br/>姓名:{this.state.obj.name}<br/>年龄:{this.state.obj.age}</p>
this.setState({ 要修改的数据 })
state = {
count: 0,
list: [1, 2, 3],
person: {
name: 'jack',
age: 18
}
}
// 【不推荐】直接修改当前值的操作:
this.state.count++
++this.state.count
this.state.count += 1
this.state.count = 1
// 只要是数组中直接修改当前数组的方法都不能用!
this.state.list.push(123)
this.state.person.name = 'rose'
// 【推荐】不是直接修改当前值,而是创建新值的操作:
this.setState({
count: this.state.count + 1,
list: [...this.state.list, 123],
person: {
...this.state.person,
// 要修改的属性,会覆盖原来的属性,这样,就可以达到修改对象中属性的目的了
name: 'rose'
}
})
目的:能够理解 setState 延迟更新数据
state = { count: 1 }
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 1
**目标:**能够掌握setState箭头函数的语法,解决多次调用依赖的问题
setState((prevState) => {})
语法setState
更新后的状态this.setState((prevState) => {
return {
count: prevState.count + 1
}
})
**目标:**能够使用setState的回调函数,操作渲染后的DOM
setState(updater[, callback])
this.setState(
(state) => ({}),
() => {console.log('这个回调函数会在状态更新后并且 DOM 更新后执行')}
)
目标:能够说出setState到底是同步的还是异步
import { Component } from 'react'
export default class App extends Component {
state = {
count: 0,
}
// 点击按钮,分别查看 情况1 和 情况2 的 render 打印次数
handleClick = () => {
// 情况1
this.setState({
count: this.state.count + 1,
})
this.setState({
count: this.state.count + 1,
})
console.log('1:', this.state.count)
// 情况2
// setTimeout(() => {
// this.setState({
// count: this.state.count + 1,
// })
// this.setState({
// count: this.state.count + 1,
// })
// console.log('2:', this.state.count)
// }, 0)
}
render() {
console.log('render')
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>add</button>
</div>
)
}
}
原因:
isBatchingUpdates
判断是直接更新this.state还是放到队列中回头再说,注意:
扩展阅读: 深入 setState 机制
目标:能够知道什么是 RESTFul API
RESTFul API
(或 REST API)是一套流行的接口设计风格,参考 RESTFul API 设计指南
使用 RESTFul API
时,常用请求方式有以下几种:
约定:请求方式是动词,接口地址使用名词
示例:
目标:能够使用 json-server 提供接口数据
json-server 文档
作用:根据 json 文件创建 RESTful 格式的 API 接口
步骤:
{
"tabs": [
{
"id": 1,
"name": "热度",
"type": "hot"
},
{
"id": 2,
"name": "时间",
"type": "time"
}
],
"list": [
{
"id": 1,
"author":"猫爷",
"comment":"来杯啤酒",
"time":"2021-11-24T03:13:40.644Z",
"attitude":0
}
],
"test":[
{
"id": 1,
"author":"张三疯",
"comment":"来杯啤酒",
"time":"2021-11-24T03:13:40.644Z",
"attitude":0
}
]
}
# 使用 npx 命令调用 json-server
# 指定用于提供数据的 json 文件
# --port 用于修改端口号
# --watch 表示监听 json 文件的变化,当变化时,可以不重启终端命令的前提下读取到 json 文件中的最新的内容
npx json-server ./data.json --port 8888 --watch
# 接口地址为:
http://localhost:8888/tabs
http://localhost:8888/list
// 5.2 生命周期的三个阶段
import React from 'react';
import axios from 'axios';
class Constructor extends React.Component {
state = {
list: []
};
componentDidMount() {
this.getList()
}
getList = async () => {
const res = await axios.get('http://localhost:8888/test')
console.log(res);
this.setState({
list: res.data
})
}
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<h2>6.1 生命周期的三个阶段</h2>
</div>
);
}
}
export default Constructor;
目标: 能够使用受控组件的方式获取文本框的值
state
中的,并且要求状态只能通过setState
进行修改 state={
name:'张三疯',
age:19,
count:99,
obj:{
name:'张三',
age:19,
}
}
<h3>4. 表单处理-受控组件</h3>
受控组件:<input type="text" value={this.state.count} onChange={(e)=>{this.setState({count:e.target.value})}}/>
import React from 'react'
import ReactDOM from 'react-dom'
/*
受控组件示例
*/
class App extends React.Component {
state = {
txt: '',
content: '',
city: 'bj',
isChecked: false
}
handleChange = e => {
this.setState({
txt: e.target.value
})
}
// 处理富文本框的变化
handleContent = e => {
this.setState({
content: e.target.value
})
}
// 处理下拉框的变化
handleCity = e => {
this.setState({
city: e.target.value
})
}
// 处理复选框的变化
handleChecked = e => {
this.setState({
isChecked: e.target.checked
})
}
render() {
return (
<div>
{/* 文本框 */}
<input type="text" value={this.state.txt} onChange={this.handleChange} />
<br/>
{/* 富文本框 */}
<textarea value={this.state.content} onChange={this.handleContent}></textarea>
<br/>
{/* 下拉框 */}
<select value={this.state.city} onChange={this.handleCity}>
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="gz">广州</option>
</select>
<br/>
{/* 复选框 */}
<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked} />
</div>
)
}
}
// 渲染组件
ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react'
import ReactDOM from 'react-dom'
/*
受控组件示例
*/
class App extends React.Component {
state = {
txt: '',
content: '',
city: 'bj',
isChecked: false
}
handleForm = e => {
// 获取当前DOM对象
const target = e.target
// 根据类型获取值
const value = target.type === 'checkbox'
? target.checked
: target.value
// 获取name
const name = target.name
this.setState({
[name]: value
})
}
render() {
return (
<div>
{/* 文本框 */}
<input type="text" name="txt" value={this.state.txt} onChange={this.handleForm} />
<br/>
{/* 富文本框 */}
<textarea name="content" value={this.state.content} onChange={this.handleForm}></textarea>
<br/>
{/* 下拉框 */}
<select name="city" value={this.state.city} onChange={this.handleForm}>
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="gz">广州</option>
</select>
<br/>
{/* 复选框 */}
<input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.handleForm} />
</div>
)
}
}
// 渲染组件
ReactDOM.render(<App />, document.getElementById('root'))
constructor() {
super()
this.txtRef = React.createRef()
}
<input type="text" ref={this.txtRef} />
<button onClick={this.getTxt}>获取文本框的值</button>
getTxt = () => {
console.log('文本框值为:', this.txtRef.current.value);
}
目标:了解为什么需要组件通讯
目标: 能够使用组件的props实现传递数据和接收数据
this.props
接收数据// 接收数据:
// props 的值就是:{ name: 'jack', age: 19 }
function Childen(props) {
return (
<div>
<h4>子组件(Childen)</h4>
<p>接收到的数据: 姓名:{props.name} 年龄:{props.age}</p>
</div>
);
}
// 传递数据:
// 可以把传递数据理解为调用函数 Hello,即:Hello({ name: 'jack', age: 19 })
<h4>函数组件获取props</h4>
<Childen name="张三疯" age="99" />
this.props
接收数据// 接收数据:
// class 组件需要通过 this.props 来获取
// 注意:this.porps 属性名是固定的!!!
class Child extends React.Component {
render() {
return (
<div>
<h4>类子组件(Child)</h4>
<p>接收到的数据: 姓名:{this.props.name} 年龄:{this.props.age}</p>
</div>
);
}
}
<h4>类组件获取props</h4>
<Child name="类组件" age="99" />
目标: 了解props的特点,知道什么是单向数据流
**目标:**掌握props中children属性的用法
const Hello = props => {
return (
<div>
该组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello>
//App.js 中:
const NavBar = props => {
return (
<div className="nav-bar">
<span><</span>
<div className="title">{props.children}</div>
</div>
)
}
export default class App extends Component {
render() {
return (
<div>
<NavBar>标题</NavBar>
<br />
<NavBar>商品</NavBar>
<br />
<NavBar>
<span style={{ color: 'red' }}>花哨的标题</span>
</NavBar>
</div>
)
}
}
//App.css 中:
.nav-bar {
display: flex;
position: relative;
background-color: #9696dc;
padding: 5px;
text-align: center;
}
.title {
position: absolute;
width: 100%;
height: 100%;
}
**目标:**校验接收的props的数据类型,增加组件的健壮性
// 假设,这是 小明 创建的 List 组件
const List = props => {
const arr = props.colors
const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
return (
<ul>{lis}</ul>
)
}
// 小红使用小明创建的 List 组件
<List colors={19} />
// 约定 List 组件的 colors 属性是数组类型
// 如果不是数组就会报错,增加组件的健壮性
App.propsTypes = {
colors: PropTypes.array
}
组件名.propTypes = {}
来给组件的props添加校验规则import PropTypes from 'prop-types'
function App(props) {
return (
<h1>Hi, {props.colors}</h1>
) }
App.propTypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors: PropTypes.array
}
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
//-----------------------------------------------------
// 添加props校验
// 属性 a 的类型: 数值(number)
// 属性 fn 的类型: 函数(func)并且为必填项
// 属性 tag 的类型: React元素(element)
// 属性 filter 的类型: 对象({area: '上海', price: 1999})
App.propTypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
tag: PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
**目标:**给组件的props提供默认值
defaultProps
可以给组件的props设置默认值,在未传入props的时候生效function App(props) {
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
) }
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 不传入pageSize属性
<App />
**目标:**能够通过类的static语法简化props校验和默认值
class Person {
name = 'zs'; // 实例属性
sayHi() {
console.log('哈哈'); // 实例方法
}
static age = 18; // 静态属性
static goodBye() {
console.log('byebye'); // 静态方法
}
}
const p = new Person();
console.log(p.name); // 访问实例属性
p.sayHi(); // 调用实例方法
console.log(Person.age); // 访问静态属性
Person.goodBye(); // 调用静态方法
class List extends Component {
static propTypes = {
colors: PropTypes.array,
gender: PropTypes.oneOf(['male', 'female']).isRequired
}
static defaultProps = {
gender: ''
}
render() {
const arr = this.props.colors
const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
return <ul>{lis}</ul>
}
}
目标:将父组件的数据传递给子组件
class Parent extends React.Component {
state = {
money: 10000
}
render() {
const { money } = this.state
return (
<div>
<h1>我是父组件:{money}</h1>
{/* 将数据传递给子组件 */}
<Child money={money} />
</div>
)
}
}
function Child(props) {
return (
<div>
{/* 接收父组件中传递过来的数据 */}
<h3>我是子组件 -- {props.money}</h3>
</div>
)
}
目标: 能够将子组件的数据传递给父组件
class Parent extends React.Component {
state = {
money: 10000
}
// 回调函数
//1. 父组件提供一个回调函数(用于接收数据)
buyPhone = price => {
this.setState({
money: this.state.money - price
})
}
render() {
const { money } = this.state
return (
<div>
<h1>我是父组件:{money}</h1>
{/* 2.将该函数作为属性的值,传递给子组件 */}
<Child money={money} buyPhone={this.buyPhone} />
</div>
)
}
}
//3. 子组件通过 props 调用回调函数
const Child = (props) => {
const handleClick = () => {
// 子组件调用父组件传递过来的回调函数,
//4. 将子组件的数据作为参数传递给回调函数
props.buyPhone(100)
}
return (
<div>
<h3>我是子组件 -- {props.money}</h3>
<button onClick={handleClick}>买手机</button>
</div>
)
}
目标: 能够理解什么是状态提升,并实现兄弟组件之间的组件通讯
index.js
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
// 导入两个子组件
import Jack from './Jack'
import Rose from './Rose'
// App 是父组件
class App extends Component {
// 1. 状态提升到父组件
state = {
msg: '',
}
changeMsg = msg => {
this.setState({
msg,
})
}
render() {
return (
<div>
<h1>我是App组件</h1>
{/* 兄弟组件 1 */}
<Jack say={this.changeMsg}></Jack>
{/* 兄弟组件 2 */}
<Rose msg={this.state.msg}></Rose>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<App />, document.getElementById('root'))
Jack.js
import React, { Component } from 'react'
export default class Jack extends Component {
say = () => {
// 修改数据
this.props.say('you jump i look')
}
render() {
return (
<div>
<h3>我是Jack组件</h3>
<button onClick={this.say}>说</button>
</div>
)
}
}
Rose.js
import React, { Component } from 'react'
export default class Rose extends Component {
render() {
return (
<div>
{/* 展示数据 */}
<h3>我是Rose组件-{this.props.msg}</h3>
</div>
)
}
}
**目标:**通过context实现跨级组件通讯
React. createContext()
创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。//Provider(提供数据) 和 Consumer(消费数据)
const { Provider, Consumer } = React.createContext()
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
<Provider value="pink">
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
**目的:**能够理解什么是组件的生命周期以及为什么需要研究组件的生命周期
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件生命周期
*/
class App extends React.Component {
constructor (props) {
super(props)
// 初始化state
this.state = {
count: 0
}
// 处理this指向问题
console.warn('生命周期钩子函数: constructor')
}
// 1 进行DOM操作
// 2 发送ajax请求,获取远程数据
componentDidMount () {
// axios.get('http://api.....')
// const title = document.getElementById('title')
// console.log(title)
console.warn('生命周期钩子函数: componentDidMount')
}
render () {
// 错误演示!!! 不要在render中调用setState()
// this.setState({
// count: 1
// })
console.warn('生命周期钩子函数: render')
return (
<div>
<h1 id="title">统计豆豆被打的次数:</h1>
<button id="btn">打豆豆</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等 |
render | 每次组件渲染都会触发 | 渲染UI(注意: 不能调用setState() ) |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1. 发送网络请求 2.DOM操作 |
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件生命周期
*/
class App extends React.Component {
constructor(props) {
super(props)
// 初始化state
this.state = {
count: 0
}
}
// 打豆豆
handleClick = () => {
// this.setState({
// count: this.state.count + 1
// })
// 演示强制更新:
this.forceUpdate()
}
render() {
console.warn('生命周期钩子函数: render')
return (
<div>
<Counter count={this.state.count} />
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component {
render() {
console.warn('--子组件--生命周期钩子函数: render')
return <h1>统计豆豆被打的次数:{this.props.count}</h1>
}
}
ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件生命周期
*/
class App extends React.Component {
constructor(props) {
super(props)
// 初始化state
this.state = {
count: 0
}
}
// 打豆豆
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<Counter count={this.state.count} />
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component {
render() {
console.warn('--子组件--生命周期钩子函数: render')
return <h1 id="title">统计豆豆被打的次数:{this.props.count}</h1>
}
// 注意:如果要调用 setState() 更新状态,必须要放在一个 if 条件中
// 因为:如果直接调用 setState() 更新状态,也会导致递归更新!!!
componentDidUpdate(prevProps) {
console.warn('--子组件--生命周期钩子函数: componentDidUpdate')
// 正确做法:
// 做法:比较更新前后的props是否相同,来决定是否重新渲染组件
console.log('上一次的props:', prevProps, ', 当前的props:', this.props)
if (prevProps.count !== this.props.count) {
// this.setState({})
// 发送ajax请求的代码
}
// 错误演示!!!
// this.setState({})
// 获取DOM
// const title = document.getElementById('title')
// console.log(title.innerHTML)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件生命周期
*/
class App extends React.Component {
constructor(props) {
super(props)
// 初始化state
this.state = {
count: 0
}
}
// 打豆豆
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
{this.state.count > 3 ? (
<p>豆豆被打死了~</p>
) : (
<Counter count={this.state.count} />
)}
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component {
componentDidMount() {
// 开启定时器
this.timerId = setInterval(() => {
console.log('定时器正在执行~')
}, 500)
}
render() {
return <h1>统计豆豆被打的次数:{this.props.count}</h1>
}
componentWillUnmount() {
console.warn('生命周期钩子函数: componentWillUnmount')
// 清理定时器
clearInterval(this.timerId)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
目标:能够说出 React Hooks是什么?
Hooks
:钩子、钓钩、钩住
Hooks
是 React v16.8 中的新增功能
作用:
注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿
React v16.8 版本前后,组件开发模式的对比:
React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
React v16.8 及其以后:
总结:
注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class
注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态
目标:能够说出为什么要有 Hooks 以及 Hooks 能解决什么问题
两个角度
mixins(混入)
、HOCs(高阶组件)
、render-props 等模式
1. 数据来源不清晰
2. 命名冲突
重构组件结构,导致组件形成 JSX 嵌套地狱问题
目标:能够理解在react中什么场景应该使用hooks
react没有计划从React中移除 class 文档
Hooks 和现有代码可以同时工作,你可以渐进式地使用他们
之前的react语法并不是以后就不用了
componentDidMount
、componentDidUpdate
、componentWillUnmount
this
相关的用法原来学习的 React 内容还是要用的
{}
、onClick={handleClick}
、条件渲染、列表渲染、样式处理等单向数据流
、状态提升
等目标:能够使用
useState
为函数组件提供状态
useState
使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了useState
作用:为函数组件提供状态(state)useState
语法:const [变量,设置变量的函数] = useState(初始值)
// const [变量,设置变量的函数] = useState(初始值)
const [stateArray, setStateArray] = useState([1, 2, 3, 4, 5]);
useState
hookuseState
函数,并传入状态的初始值useState
函数的返回值中,拿到状态和修改状态的函数import React from 'react';
import './index.css';
// 1. 导入 `useState` hook
import { useState } from 'react';
function App() {
// 参数:状态初始值
// 返回值:stateArray 是一个数组
// const [变量,设置变量的函数] = useState(初始值)
// 1.简单类型
// 2. 调用 `useState` 函数,并传入状态的初始值
const [ count, setCount ] = useState(0);
console.log(count);
const [ isShow, setShow ] = useState(false);
console.log(isShow);
// 2.复杂类型
const [ stateArray, setStateArray ] = useState([ 1, 2, 3, 4, 5 ]);
console.log(stateArray, setStateArray);
// 索引 0 表示:状态值(state)
const state = stateArray[0];
console.log(state);
// 索引 1 表示:修改状态的函数(setState)
const setState = stateArray[1];
console.log(setState);
const [ stateObj, setStateObj ] = useState({ name: '张三疯', age: 18 });
console.log(stateObj, setStateObj);
return (
<div className="App">
<h1>useState hook</h1>
<h2>useState-基本使用</h2>
<p>useState使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了</p>
<p>useState作用:为函数组件提供状态(state)</p>
{/* 展示状态值 */}
<p>{count}</p>
{/* 点击按钮,让状态值 +1 */}
<button onClick={() => setCount(count + 1)}>+1</button>
<p>{isShow ? '加载中' : '加载完成'}</p>
<button
onClick={() => {
setShow(!isShow);
}}
>
修改isShow
</button>
<p>
姓名:{stateObj.name} ---- 年龄:{stateObj.age}
</p>
</div>
);
}
export default App;
**目标:**能够使用数组解构简化useState的使用
const arr = ['aaa', 'bbb']
const a = arr[0] // 获取索引为 0 的元素
const b = arr[1] // 获取索引为 1 的元素
const arr = ['aaa', 'bbb']
const [a, b] = arr
// a => arr[0]
// b => arr[1]
// 解构出来的名称,可以是任意变量名
const [state, setState] = arr
useState
的使用import { useState } from 'react'
const Count = () => {
// 解构:
const [count, setCount] = useState(0)
return (
<div>
<h1>计数器:{state}</h1>
<button onClick={() => setState(state + 1)}>+1</button>
</div>
)
}
// 解构出来的名称可以是任意名称,比如:
const [state, setState] = useState(0)
const [age, setAge] = useState(0)
const [count, setCount] = useState(0)
目标:能够在函数组件中获取和修改状态
useState
提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用const Counter = () => {
const [user, setUser] = useState({ name: 'jack', age: 18 })
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
</div>
)
}
setCount(newValue)
是一个函数,参数表示:新的状态值替换
旧值const Counter = () => {
const [user, setUser] = useState({ name: 'jack', age: 18 })
const onAgeAdd = () => {
setUser({
...user,
age: user.age + 1
})
}
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<button onClick={onAgeAdd}>年龄+1</button>
</div>
)
}
总结:
**目标:**能够说出使用功能
useState
之后,组件的更新过程
useState(0)
将传入的参数作为状态初始值,即:0setCount(count + 1)
修改状态,因为状态发生改变,所以,该组件会重新渲染useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1import { useState } from 'react'
const Count = () => {
const [count, setCount] = useState(0)
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
**目标:**能够为函数组件提供多个状态
如何为函数组件提供多个状态?
useState
Hook 多次即可,每调用一次 useState Hook 可以提供一个状态useState Hook
多次调用返回的 [state, setState],相互之间,互不影响useState 等 Hook 的使用规则:
可以通过开发者工具进行查看组件的 hooks:
**目标:**能够说出什么是副作用
side effect使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了
作用:处理函数组件中的副作用(side effect)
问题:副作用(side effect)是什么?
类比,对于 999 感冒灵感冒药来说:
理解:副作用是相对于主作用来说的,一个功能(比如,函数)除了主作用,其他的作用就是副作用 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
常见的副作用(side effect):数据(Ajax)请求、手动修改 DOM、localStorage、console.log 操作等
总结:
对于react组件来说,除了渲染UI之外的其他操作,都可以称之为副作用
const Count = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
axios.post('http://xxx')
return (
计数器:{count}
)
}
**目标:**能够在函数组件中操作DOM(处理副作用)
使用场景:当你想要在函数组件中,处理副作用(side effect)时就要使用 useEffect Hook 了
useEffect作用:处理函数组件中的一些副作用(side effect)
注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用
语法:
import { useEffect } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('callback回调函数执行了', count + 1);
document.title = `当前已点击 ${count} 次`
})
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
**目标:**能够设置 useEffect 的依赖只在 count 变化时执行相应的 effect
问题:如果组件中有另外一个状态,另一个状态更新时,刚刚的 effect 回调也会执行
默认情况:只要状态发生更新 useEffect 的 effect 回调就会执行
性能优化:跳过不必要的执行,只在 count 变化时,才执行相应的 effect
语法:
useEffect(() => {
document.title = `当前已点击 ${count} 次`
}, [count])
**目标:**能够理解不正确使用依赖项的后果
const App = () => {
const [count, setCount] = useState(0)
// 错误演示:
useEffect(() => {
document.title = '点击了' + count + '次'
}, [])
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
useEffect完全指南:https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
**目标:**能够设置useEffect的依赖,让组件只有在第一次渲染后会执行
useEffect 的第二个参数,还可以是一个空数组([]) ,表示只在组件第一次渲染后执行 effect
使用场景:
语法:
注意:
**目标:**能够在组件卸载的时候清除注册的事件
effect 的返回值是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
清理函数的执行时机:
componetWillUnmount
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
// 这个返回的函数,会在该组件卸载时来执行
// 因此,可以去执行一些清理操作,比如,解绑 window 的事件、清理定时器 等
return () => window.removeEventListener('resize', handleResize)
}, [])
**目标:**能够说出useEffect的 4 种使用使用方式
// 1
// 触发时机:1 第一次渲染会执行 2 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {});
// 2(使用频率最高)
// 触发时机:只在组件第一次渲染时执行
// componentDidMount
useEffect(() => {}, []);
// 3(使用频率最高)
// 触发时机:1 第一次渲染会执行 2 当 count 变化时会再次执行
// componentDidMount + componentDidUpdate(判断 count 有没有改变)
useEffect(() => {}, [ count ]);
// 4
useEffect(() => {
// 返回值函数的执行时机:组件卸载时
// 在返回的函数中,清理工作
return () => {
// 相当于 componentWillUnmount
};
}, []);
useEffect(
() => {
// 返回值函数的执行时机:1 组件卸载时 2 count 变化时
// 在返回的函数中,清理工作
return () => {};
},
[ count ]
);
**目的:**能够在函数组件中通过useEffect发送ajax请求
在组件中,可以使用 useEffect Hook 来发送请求(side effect)获取数据
注意:effect 只能是一个同步函数,不能使用 async
因为如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
// 错误演示:不要给 effect 添加 async
useEffect(async () => {
const res = await axios.get('http://xxx')
return () => {}
}, [])
// 正确使用
useEffect(() => {
const loadData = async () => {
const res = await axios.get('http://xxx')
}
loadData()
return () => {}
}, [])
class Counter extends React.Component {
state = {
count: 0
}
handleClick = () => {
// 1 第一个参数是对象
this.setState({
count: this.state.count + 1
})
// 2 第一个参数是回调函数
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
// 3 可以有第二个参数
this.setState({
count: this.state.count + 1
}, () => {})
}
}
const [count, setCount] = useState(0)
// 此处的 setCount 和 class 中的 this.setState 作用是相同的,都是用来更新状态
// 1 参数就是要更新的状态值
setCount(count + 1)
// 2 参数是回调函数。回调函数的参数,表示:上一次的状态值(count 上一次的值)
setCount(prevCount => prevCount + 1)
**目标:**能够使用自定义hooks实现状态的逻辑复用
内置 Hooks 为函数组件赋予了 class 组件的功能;在此之上,自定义 Hooks 针对不同组件实现不同状态逻辑复用。
**目标:**能够通过useContext hooks实现跨级组件通讯
useContext
hookContext
对象App.jsx
中,使用 useContext
hook,获取 Context
对象import { useContext } from 'react'
const { color } = useContext(ColorContext)
useContext Hook
与
的区别:获取数据的位置不同
:在 JSX 中获取 Context 共享的数据const ColorContext = createContext()
const Child = () => {
// 在普通的 JS 代码中:
const { color } = useContext(ColorContext)
return (
<div>
useContext 获取到的 color 值:{ color }
{/* 在 JSX 中: */}
<ColorContext.Consumer>
{color => <span>共享的数据为:{color}</span>}
</ColorContext.Consumer>
</div>
)
}
**目标:**能够使用useRef操作DOM
// inputRef => { current }
const inputRef = useRef(null)
import { useRef } from 'react'
const App = () => {
// 1 使用useRef能够创建一个ref对象
const inputRef = useRef(null)
const add = () => {
// 3 通过 inputRef.current 来访问对应的 DOM
console.log(inputRef.current.value)
inputRef.current.focus()
}
return (
<section className="todoapp">
{/* 2 将 ref 对象设置为 input 的 ref 属性值。目的:将 ref 对象关联到 input 对应的 DOM 对象上 */}
<input type="text" placeholder="请输入内容" ref={inputRef} />
<button onClick={add}>添加</button>
</section>
)
}
export default App
目标:能够说出为什么需要使用redu
redux 中文文档
redux 英文文档
主要的区别:组件之间的通讯问题
不使用 Redux (图左边) :
使用 Redux (图右边):
目标:能够在react项目中准备redux开发环境
npx create-react-app redux-basic
yarn start
yarn add redux
目标:能够理解redux三个核心概念的职责
Redux 代码被分为三个核心概念:action/reducer/store
类比生活中的例子来理解三个核心概念:
目标:能够定义一个最基本的action
{type:'命令', payload:'载荷'}
type
属性,用于区分动作的类型payload
有效载荷,也就是附带的额外的数据),配合该数据来完成相应功能 const Add = 'add'
const Sub = 'sub'
function add () {
return {
type: Add
}
}
function sub () {
return {
type: Sub
}
}
目标:能够使用函数去创建一个action
语法: (payload)=>({type:'命令', payload:'载荷'})
// 加/减
// 命令:动作
const ADD = 'ADD', SUB = 'SUB'
// 修改state状态会使用
export function addAction (payload) {
return {
type: ADD,
payload
}
}
export function subAction (payload) {
return {
type: SUB,
payload
}
}
目标:能够掌握reducer的基本写法
(prevState, action) => newState
// 初始值
const initialState = {
num: 0
}
export function numReducer (state = initialState, action) {
let newState = { ...state }
switch (action.type) {
case ADD:
newState.num++
return newState
case SUB:
newState.num--
return newState
default:
return state
}
}
目标:通过store关联action和reducer
store:仓库,Redux 的核心,整合 action 和 reducer
特点:
store.getState()
store.dispatch(action)
const store = createStore(reducer)
其他 API:
const unSubscribe = store.subscribe(() => {})
unSubscribe()
目标:了解纯函数的特点
函数式编程,是一种编程范式。主要思想是:把运算过程尽量写成一系列嵌套的函数调用
纯函数是函数式编程中的概念,对于纯函数来说,相同的输入总是得到相同的输出
参考资料:函数式编程初探
纯函数的演示:
// == 纯函数 ==
const add = () => {
return 123
}
add() // 123
add() // 123
const add = (num1, num2) => {
return num1 + num2
}
add(1, 2) // 3
add(1, 2) // 3
// == 不是纯函数 ==
const add = () => {
return Math.random()
}
add() // 0.12311293827497123
add() // 0.82239841238741814
// 无副作用
const add = (num1, num2) => {
return num1 + num2
}
add(1, 3)
// 有副作用:
let c = 0
const add = (num1, num2) => {
// 函数外部的环境产生了影响,所以是有副作用的
c = 1
return num1 + num2
}
add(1, 3)
// 有副作用
const add = (num1, num2) => {
// 因为该操作,会让浏览器控制额外的打印一些内容
console.log('add')
return num1 + num2
}
add(1, 3)
目标:能够说出 redux 代码的执行流程
store.dispatch(action)
更新状态10
)和 action({ type: 'ADD' }
),计算出新的状态并返回import { createStore } from 'redux'
// 初始值
const initialState = {
num: 10
}
// 2. 干活:reducer创建store使用
export function numReducer (state = initialState, action) {
let newState = { ...state }
// console.log('默认执行一次:', newState)
switch (action.type) {
case ADD:
newState.num++
return newState
case SUB:
newState.num -= action.params
return newState
default:
return state
}
}
// 3. 创建 store
// 参数为:reducer 函数
const store = createStore(numReducer)
console.log('状态值为:', store.getState()) // 10
// 发起更新状态:
// 参数: action 对象
store.dispatch({ type: 'ADD' })
// 相当于: reducer(10, { type: 'increment' })
console.log('更新后:', store.getState()) // 11
目标:能够说出为什么需要使用react-redux
目标:使用react-redux库在react中使用redux管理状态
react-redux 文档
yarn add react-redux
// 导入 Provider 组件
import { Provider } from 'react-redux'
// 导入创建好的 store
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#root')
)
目标:能够使用 useSelector hook 获取redux中共享的状态
useSelector
:获取 Redux 提供的状态数据import { useSelector } from 'react-redux'
// 计数器案例中,Redux 中的状态是数值,所以,可以直接返回 state 本身
const count = useSelector(state => state)
// 比如,Redux 中的状态是个对象,就可以:
const list = useSelector(state => state.list)
import { useSelector } from 'react-redux'
const App = () => {
const count = useSelector(state => state)
return (
<div>
<h1>计数器:{count}</h1>
<button>数值增加</button>
<button>数值减少</button>
</div>
)
}
useDispatch
:拿到 dispatch 函数,分发 action,修改 redux 中的状态数据import { useDispatch } from 'react-redux'
// 调用 useDispatch hook,拿到 dispatch 函数
const dispatch = useDispatch()
// 调用 dispatch 传入 action,来分发动作
dispatch( action )
import { useDispatch } from 'react-redux'
const App = () => {
const dispatch = useDispatch()
return (
<div>
<h1>计数器:{count}</h1>
{/* 调用 dispatch 分发 action */}
<button onClick={() => dispatch(increment(2))}>数值增加</button>
<button onClick={() => dispatch(decrement(5))}>数值减少</button>
</div>
)
}
目标:能够说出redux数据流动过程
目标:能够组织redux的代码结构
在使用 Redux 进行项目开发时,不会将 action/reducer/store 都放在同一个文件中,而是会进行拆分
可以按照以下结构,来组织 Redux 的代码:
/store --- 在 src 目录中创建,用于存放 Redux 相关的代码
/actions --- 存放所有的 action
/reducers --- 存放所有的 reducer
index.js --- redux 的入口文件,用来创建 store
目标:能够知道为什么要抽离 action type
Action Type 指的是:action 对象中 type 属性的值
Redux 项目中会多次使用 action type,比如,action 对象、reducer 函数、dispatch(action) 等
目标:集中处理 action type,保持项目中 action type 的一致性
action type 的值采用:'domain/action'(功能/动作)形式
,进行分类处理,比如
'counter/increment'
表示 Counter 功能中的 increment 动作'login/getCode'
表示登录获取验证码的动作'profile/get'
表示获取个人资料步骤:
actionTypes
目录或者 constants
目录,集中处理
// actionTypes 或 constants 目录:
const add = 'counter/increment'
const sub = 'counter/decrement'
export { add, sub }
// --
// 使用:
// actions/index.js
import * as types from '../acitonTypes'
// reducers/index.js
import * as types from '../acitonTypes'
const reducer = (state, action) => {
switch (action.type) {
case types.add:
return state + 1
case types.sub:
return state - action.payload
default:
return state
}
}
domain/action
命名方式强烈推荐!目标:能够合并redux的多个reducer为一个根reducer
随着项目功能变得越来越复杂,需要 Redux 管理的状态也会越来越多
此时,有两种方式来处理状态的更新:
推荐:使用多个 reducer(第二种方案) ,每个 reducer 处理的状态更单一,职责更明确
此时,项目中会有多个 reducer,但是 store 只能接收一个 reducer,因此,需要将多个 reducer 合并为一根 reducer,才能传递给 store
合并方式:使用 Redux 中的 combineReducers
函数
注意:合并后,Redux 的状态会变为一个对象,对象的结构与 combineReducers 函数的参数结构相同
{ a: aReducer 处理的状态, b: bReducer 处理的状态 }
import { combineReducers } from 'redux'
// 计数器案例,状态默认值为:0
const aReducer = (state = 0, action) => {}
// Todos 案例,状态默认值为:[]
const bReducer = (state = [], action) => {}
// 合并多个 reducer 为一个 根reducer
const rootReducer = combineReducers({
a: aReducer,
b: bReducer
})
// 创建 store 时,传入 根reducer
const store = createStore(rootReducer)
// 此时,合并后的 redux 状态: { a: 0, b: [] }
注意:虽然在使用 combineReducers
以后,整个 Redux 应用的状态变为了对象
,但是,对于每个 reducer 来说,每个 reducer 只负责整个状态中的某一个值
也就是:每个reducer只负责自己要处理的状态
举例:
loginReducer
处理的状态只应该是跟登录相关的状态profileReducer
处理的状态只应该是跟个人资料相关的状态合并 reducer 后,redux 处理方式:只要合并了 reducer,不管分发什么 action,所有的 reducer 都会执行一次。各个 reducer 在执行的时候,能处理这个 action 就处理,处理不了就直接返回上一次的状态。所以,我们分发的某一个 action 就只能被某一个 reducer 来处理,也就是最终只会修改这个 reducer 要处理的状态,最终的表现就是:分发了 action,只修改了 redux 中这个 action 对应的状态!
目标:能够知道什么状态可以放在redux中管理
不同状态的处理方式:
将所有的状态全部放到 redux 中,由 redux 管理
只将某些状态数据放在 redux 中,其他数据可以放在组件中,比如:
目标: 能够理解为什么需要redux中间件
目标:能够理解中间件的触发时机
Redux 中间件执行时机:在 dispatching action 和到达 reducer 之间。
使用中间件:dispatch(action) => 执行中间件代码 => reducer
store.dispatch()
就是 Redux 库自己提供的 dispatch 方法,用来发起状态更新store.dispatch()
就是中间件封装处理后的 dispatch,但是,最终一定会调用 Redux 自己的 dispatch 方法发起状态更新目标:能够使用redux-logger中间件记录日志
yarn add redux-logger
applyMiddleware
函数 import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import rootReducer from './reducers'
const store = createStore(
rootReducer,
applyMiddleware(logger)
)
目标:能够使用redux-thunk中间件处理异步操作
redux-thunk
中间件可以处理函数形式的 action
。因此,在函数形式的 action 中就可以执行异步操作// 函数形式的 action
const thunkAction = () => {
return (dispatch, getState) => {}
}
// 解释:
const thunkAction = () => {
// 注意:此处返回的是一个函数,返回的函数有两个参数:
// 第一个参数:dispatch 函数,用来分发 action
// 第二个参数:getState 函数,用来获取 redux 状态
return (dispatch, getState) => {
setTimeout(() => {
// 执行异步操作
// 在异步操作成功后,可以继续分发对象形式的 action 来更新状态
}, 1000)
}
}
redux-thunk
中间件前后对比:
// 1 普通 action 对象
{ type: 'counter/increment' }
dispatch({ type: 'counter/increment' })
// 2 action creator
const increment = payload => ({ type: 'counter/increment', payload })
dispatch(increment(2))
// 1 对象:
// 使用 action creator 返回对象
const increment = payload => ({ type: 'counter/increment', payload })
// 分发同步 action
dispatch(increment(2))
// 2 函数:
// 使用 action creator 返回函数
const incrementAsync = () => {
return (dispatch, getState) => {
// ... 执行异步操作代码
}
}
// 分发异步 action
dispatch(incrementAsync())
使用步骤
yarn add redux-thunk
store/index.js 中:
// 导入 thunk 中间件
import thunk from 'redux-thunk'
// 将 thunk 添加到中间件列表中
// 知道:如果中间件中使用 logger 中间件,logger 中间件应该出现在 applyMiddleware 的最后一个参数
const store = createStore(rootReducer, applyMiddleware(thunk, logger))
export const clearDoneAsync = () => {
return (dispatch) => {
// 处理异步的代码:1 秒后再清理已完成任务
setTimeout(() => {
dispatch(clearDone())
}, 1000)
}
}
import { clearDoneAsync } from '../store/actions/todos'
const TodoFooter = () => {
return (
// ...
<button
className="clear-completed"
onClick={() => dispatch(clearDoneAsync())}
>
Clear completed
</button>
)
}
目标:能够使用chrome开发者工具调试跟踪redux状态
yarn add redux-devtools-extension
import thunk from 'redux-thunk'
+ import { composeWithDevTools } from 'redux-devtools-extension'
- const store = createStore(rootReducer, applyMiddleware(logger, thunk))
+ const store = createStore(reducer, composeWithDevTools(applyMiddleware(logger, thunk)))
export default store
// 简化写法:
// store 表示:redux 的 store
// next 表示:下一个中间件,如果只使用一个中间,那么 next 就是 store.dispatch(redux 自己的 dispatch 函数)
// action 表示:要分发的动作
const logger = store => next => action => {
console.log('prev state:', store.getState()) // 更新前的状态
// 记录日志代码
console.log('dispatching', action)
// next 表示原始的 dispatch
// 也就是:logger中间件包装了 store.dispatch
let result = next(action)
// 上面 next 代码执行后,redux 状态就已经更新了,所以,再 getState() 拿到的就是更新后的最新状态值
// 记录日志代码
console.log('next state', store.getState()) // 更新后的状态
return result
}
// 完整写法:
const logger = store => {
return next => {
return action => {
// 中间件代码写在这个位置:
}
}
}
目标:能够使用 react 路由切换页面
yarn add [email protected]
// 2. 导入路由的三个核心组件:Router / Route / Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
// 3 使用Router组件包裹整个应用
const App = () => (
<Router>
<div>
<h1>React路由基础</h1>
{/* 4 指定路由入口 */}
<Link to="/first">页面一</Link>
{/* 5 指定路由出口 */}
<Route path="/first" component={First} />
</div>
</Router>
)
{/* 4 指定路由入口 */}
<Link to="/first">页面一</Link>
{/* 5 指定路由出口 */}
<Route path="/first" component={First} />
目标:能够知道 react 路由有两种模式
Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:
HashRouter
hashchange
事件来实现的BrowserRouter
popstate
事件来实现的import { BrowserRouter, HashRouter} from 'react-router-dom'
const App = () => {
return (
// hash模式(history模式使用BrowserRouter包裹)
<HashRouter>
// 配置路由规则...
</HashRouter>
)
}
// to属性:浏览器地址栏中的pathname(location.pathname)
<Link to="/first">页面一</Link>
path
属性,指定路由规则component
属性,指定要渲染的组件children
子节点,指定要渲染的组件// path属性:路由规则
// component属性:展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪
// 用法一:使用 component 属性指定要渲染的组件
<Route path="/search" component={Search} />
// 用法二:使用 children 指定要渲染的组件
<Route path="/search">
<Search />
</Route>
注意:对于 Route 来说,如果路由规则匹配成功,那么,就会渲染对应组件;否则,渲染 null 或者说不渲染任何内容
对于 Route 组件来说,path
属性是可选的:如果 Route 组件没有 path 属性,表示:该路由永远匹配成功,一定会渲染该组件
目标:能够说出 react 路由切换页面的执行过程
目标:能够按钮的点击事件中跳转路由
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
useHistory
hook 来拿到路由提供的 history 对象,用于获取浏览器历史记录的相关信息。 history.push(path:string | {pathname:string, state:object})
history.replace(path:string | {pathname:string, state:object})
import { useHistory } from 'react-router-dom'
const Login = () => {
const history = useHistory()
const onLogin = () => {
// ...
history.push('/home')
}
return (
<button onClick={onLogin}>登录</button>
)
}
<Route path="/" component={Home} />
目标:能够说出路由的两种匹配模式
<Link to="/login">登录页面</Link> // 匹配成功
<Route path="/" component={Home} /> // 匹配成功
问题:默认路由任何情况下都会展示,如何避免这种问题?
exact
属性,让其变为精确匹配模式// 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />