本文参考教程:[尚硅谷2021版React技术全家桶]
本文上接:[React学习笔记1(React概念)]
xxx脚手架可以快速搭建一个基于xxx库的模板项目(包含自定义配置、依赖管理和一个简单demo)
脚手架特点:模块化、组件化、工程化
(1)安装Node.js。
进入[Node.js官网],下载长期支持版并安装(一直下一步即可)。
(2)运行cmd(win键+r键输入cmd),输入以下内容(最后输出All packages installed
即为成功安装)
# 安装淘宝npm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 全局安装create-react-app
cnpm install -g create-react-app
(3)使用cd 绝对路径
切换到想创项目的目录,然后使用命令create-react-app 项目名
(示例项目名是hello-react)
项目名最好不要使用特殊字符和中文
(4)进入项目文件夹:cd 项目名
(示例项目名是hello-react)
(5)启动项目:npm start
|- hello-react【项目文件夹】
|- node_modules【依赖文件夹】
|- public【静态资源文件夹】
|- favicon.icon【网站页签图标】
|- index.html【主页面,路径固定文件名固定】
|- logo192.png【logo图】
|- logo512.png【logo图】
|- manifest.json【应用加壳的配置文件】
|- robots.txt【爬虫协议文件】
|- src【源码文件夹】
|- App.css【App组件的样式】
|- App.js【App组件,其他组件都是该组件的子组件】
|- App.test.js【用于给App做测试】
|- index.css【样式】
|- index.js【入口文件,路径固定文件名固定】
|- logo.svg【logo图】
|- reportWebVitals.js【页面性能分析文件(需要web-vitals库的支持)】
|- setupTests.js【组件单元测试的文件(需要jest-dom库的支持)】
一些解释与补充:
- public文件夹中只有index.html一个.html文件,因为React是SPA(Single Page web Application单页web应用),只有一个页面,页面变化靠组件。(找不到资源时会显示此页面)
- index.html文件内容解释:
<head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="red" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React Apptitle> head> <body> <noscript>You need to enable JavaScript to run this app.noscript> <div id="root">div> body>
- 项目启动时读取的文件顺序
(1) src/index.js【读取入口文件(如果index.css有样式这一步也会读取)】
(2) src/APP.js【获取根组件】
(3) public/index.html【获取容器】
index.html
<html> <head> <meta charset="UTF-8" /> <title>这是标题title> head> <body> <div id="root">div> body> html>
index.js
// 引入react核心库 import React from 'react' // 引入ReactDOM import ReactDOM from 'react-dom' // 引入App组件 import App from './App' // 渲染App到页面 ReactDOM.render(<App/>,document.getElementById('root'))
App.js
// 创建“外壳”组件App import React from 'react' // 创建App组件 class App extends React.Component{ render(){ return ( <div> Hello React </div> ) } } // 暴露App组件 export default APP
模块化步骤(规范写法):具体可以看下面的代码
- 将组件的后缀从.js改为.jsx(代码不用改)
- 在src下建立components文件夹,在components文件夹下建立组件文件夹(这里建立了Hello文件夹)
- 组件文件夹下index.jsx代表组件,index.module.css代表组件样式
src/App.jsx:一、简化写法。二、将输出Hello React,改为输出Hello模块。
// 创建“外壳”组件App,这里的{Component}不是解构赋值而是因为'react'中暴露了Component函数 import React,{ Component} from 'react' // 导入组件,组件如果是文件夹中的index.jsx则可以省略(.js和.jsx的后缀可以省略) import Hello from './components/Hello' // 创建并暴露App组件 export default class App extends Component{ render(){ return ( <div> // 使用Hello组件 <Hello/> </div> ) } }
src/components/Hello/index.jsx:Hello组件
import React,{ Component} from 'react' // xxx.module.css的文件可以导入为一个类,指定文件中样式是这个类调用的,防止因为类名相同导致的样式覆盖 import hello from './index.module.css' export default class Hello extends Component{ render(){ // hello.title指定是hello类的title类的样式 return <h2 className={ hello.title}>Hello,React!</h2> } }
src/components/Hello/index.module.css:Hello样式
.title{ background-color: orange; }
代码模板:IDE中在.js或.jsx文件夹中输入下面左侧代码,然后回车
rcc
:ReactClassComponent【React类式组件】,自动生成index.js中的类式组件模板
rfc
:ReactFunctionComponent【React函数式组件】,自动生成index.js中的函数式组件模板
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
(1) 动态显示初始化数据
a. 数据类型
b. 数据名称
c. 保存在哪个组件中
(2) 交互(从绑定事件监听开始)
成品效果图:
教程流程视频(建议初学者看看):[P56]
(该部分理解后,可以以后需要的时候回来复制即可)
public/index.html
<html> <head> <meta charset="UTF-8" /> <title>这是标题title> head> <body> <div id="root">div> body> html>
src/index.js
// 引入react核心库 import React from 'react' // 引入ReactDOM import ReactDOM from 'react-dom' // 引入App import App from './App' ReactDOM.render(<App/>,document.getElementById('root'))
src/App.jsx
import React, { Component } from 'react' import Header from './components/Header' import List from './components/List' import Footer from './components/Footer' import './App.css' export default class App extends Component { // 状态在哪里,操作状态的方法就在哪里 // 初始化状态 state = { todos:[ { id:'001',name:'吃饭',done:true}, { id:'002',name:'睡觉',done:true}, { id:'003',name:'打代码',done:false}, { id:'004',name:'逛街',done:false} ]} // addTodo用于添加一个todo,接收的参数是todo对象 addTodo = (todoObj)=>{ // 获取原todos const { todos} = this.state // 追加一个todo const newTodos = [todoObj,...todos] // 更新状态 this.setState({ todos:newTodos}) } // updateTodo用于更新一个todo对象 updateTodo = (id,done)=>{ // 获取状态中的todos const { todos} = this.state // 匹配处理数据 const newTodos = todos.map((todoObj)=>{ if(todoObj.id === id) return { ...todoObj,done} else return todoObj }) this.setState({ todos:newTodos}) } // deleteTodo用于删除一个todo对象 deleteTodo = (id)=>{ // 获取原来的todos const { todos} = this.state // 删除指定id的todo对象 const newTodos = todos.filter((todoObj)=>{ return todoObj.id !== id }) // 更新状态 this.setState({ todos:newTodos}) } // checkAllTodo用于全选 checkAllTodo = (done)=>{ // 获取原来的todos const { todos} = this.state // 加工数据 const newTodos = todos.map((todoObj)=>{ return { ...todoObj,done} }) // 更新状态 this.setState({ todos:newTodos}) } // clearAllDone用于清除所有已完成的 clearAllDone = ()=>{ // 获取原来的todos const { todos} = this.state // 过滤数据 const newTodos = todos.filter((todoObj)=>{ return !todoObj.done }) // 更新状态 this.setState({ todos:newTodos}) } render() { const { todos} = this.state return ( <div className="todo-container"> <div className="todo-wrap"> <Header addTodo={ this.addTodo}/> <List todos={ todos} updateTodo={ this.updateTodo} deleteTodo={ this.deleteTodo}/> <Footer todos={ todos} checkAllTodo={ this.checkAllTodo} clearAllDone={ this.clearAllDone}/> </div> </div> ) } }
src/components/Header/index.jsx
import React, { Component } from 'react' import PropTypes from 'prop-types' import { nanoid } from 'nanoid' import './index.css' export default class Header extends Component { // 对接收的props进行:类型、必要性的限制 static propTypes = { addTodo: PropTypes.func.isRequired } // 键盘事件的回调 handleKeyUp = (event) => { // 解构赋值获取keyCode,target const { keyCode, target } = event // 判断是否是回车按键 if (keyCode !== 13) return //添加的todo名字不能为空 if (target.value.trim() === '') { alert('输入不能为空') return } // 准备好一个todo对象 const todoObj = { id: nanoid(), name: target.value, done: false } // 将todoObj传递给App this.props.addTodo(todoObj) // 清空输入 target.value = '' } render() { return ( <div className="todo-header"> <input onKeyUp={ this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" /> </div> ) } }
src/components/List/index.jsx
import React, { Component } from 'react' import PropTypes from 'prop-types' import Item from '../Item' import './index.css' export default class List extends Component { // 对接收的props进行:类型、必要性的限制 static propTypes = { todos:PropTypes.array.isRequired, updateTodo:PropTypes.func.isRequired, deleteTodo:PropTypes.func.isRequired, } render() { // 解构赋值,简写如todos=this.props.todos等 const { todos,updateTodo,deleteTodo} = this.props return ( <ul className="todo-main"> { // 遍历todos todos.map( todo =>{ // {...todo}表示将todo对象的所有属性都传入,即id={todo.id} name={todo.name}等 return <Item key={ todo.id} { ...todo} updateTodo={ updateTodo} deleteTodo={ deleteTodo}/> }) } </ul> ) } }
src/components/Item/index.jsx
import React, { Component } from 'react' import './index.css' export default class Item extends Component { state = { mouse:false} //标识鼠标移入、移出 // 鼠标移入、移出的回调 handleMouse = (flag)=>{ return ()=>{ this.setState({ mouse:flag}) } } // 勾选、取消勾选某一个todo的回调 handleCheck = (id)=>{ return (event)=>{ this.props.updateTodo(id,event.target.checked) } } // 删除一个todo的回调 handleDelete = (id)=>{ if(window.confirm('确定删除吗?')){ this.props.deleteTodo(id) } } render() { const { id,name,done} = this.props const { mouse} = this.state return ( <li style={ { backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={ this.handleMouse(true)} onMouseLeave={ this.handleMouse(false)}> <label> <input type="checkbox" checked={ done} onChange={ this.handleCheck(id)}/> <span>{ name}</span> </label> <button onClick={ ()=> this.handleDelete(id) } className="btn btn-danger" style={ { display:mouse?'block':'none'}}>删除</button> </li> ) } }
src/components/Footer/index.jsx
import React, { Component } from 'react' import './index.css' export default class Footer extends Component { // 全选checkbox的回调 handleCheckAll = (event)=>{ this.props.checkAllTodo(event.target.checked) } // 清除已完成任务的回调 handleClearAllDone = ()=>{ this.props.clearAllDone() } render() { const { todos} = this.props // 已完成的个数 const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0) // 总数 const total = todos.length return ( <div className="todo-footer"> <label> <input type="checkbox" onChange={ this.handleCheckAll} checked={ doneCount === total && total !== 0 ? true : false}/> </label> <span> <span>已完成{ doneCount}</span> / 全部{ total} </span> <button onClick={ this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button> </div> ) } }
src/App.css
body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
src/components/Header/index.css
.todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); }
src/components/List/index.css
.todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; }
src/components/Item/index.css
li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; }
src/components/Footer/index.css
.todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; }
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
(1) 某个组件使用:放在其自身的state中
(2) 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)- 关于父子之间通信:
(1) 【父组件】给【子组件】传递数据:通过props传递
(2) 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数- 注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
该部分所有示例的客户端端口都是http://localhost:3000;服务器端口都是http://localhost:5000
React本身只关注于界面, 并不包含发送ajax请求的代码,因此想要从后端获取数据(一般是json数据)需要通过第三方库(如ajax库)或自己封装。
目前有jQuery ajax、Axios、Fetch三个选择,主流是使用Axios。[扩展阅读:Jquery ajax, Axios, Fetch区别之我见]
安装(命令行下):npm install axios --save
jQuery ajax、Axios都是对XMLHttpRequest的封装。Fetch与XMLHttpRequest同级别。
src/App.jsx:简单的Axios使用示例
import React, { Component } from 'react' import axios from 'axios' export default class App extends Component { getStudentData = ()=>{ axios.get('http://localhost:3000/api1/students').then( response => { console.log('成功了',response.data);}, error => { console.log('失败了',error);} ) } render() { return ( <div> <button onClick={ this.getStudentData}>点我获取学生数据</button> </div> ) } }
扩展:Ajax携带参数的几种方式
- query
- params
- body
(1) urlencode
(2) json
Axios虽然是最推荐的,但是Axios有个小问题:
允许本地跨端口发送(从http://localhost:3000发请求到http://localhost:5000)
不允许本地跨端口接收(从http://localhost:5000返回的响应到http://localhost:3000会被Axios拦截)
不解决这个问题,就难以在本地进行测试,所以我们可以使用如下特性:
Axios允许本地同端口发送和接收
(从http://localhost:3000发请求到http://localhost:3000
和从http://localhost:3000返回的响应到http://localhost:3000都是允许的)
我们可以使用这个特性,在请求与接受中加入一个代理(代理的端口与发送请求的端口相同)。
React中有两种实现代理的方法:
(1)在package.json中追加如下配置
"proxy": "http://localhost:5000"
(2)src/App.jsx中的请求改为:(请求端口由5000改为3000)
axios.get('http://localhost:3000/students').then(
response => {
console.log('成功了',response.data);},
error => {
console.log('失败了',error);}
)
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
(1)建立项目根目录/src/setupProxy.js文件
(2)在文件中编写(因为React会将该文件加到webpack的配置中(Node.js环境),所以要使用CJS语法,不是ES6语法)
如下代码不用记忆,需要的时候复制一下就行
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
// api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
proxy('/api1',{
// 配置转发目标地址(能返回数据的服务器地址)
target:'http://localhost:5000',
/**
* 控制服务器收到的请求头中Host的值
*
* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
* changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
* changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
changeOrigin:true,
// 去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
pathRewrite:{
'^/api1':''}
}),
)
}
(3)src/App.jsx中的请求改为:(请求端口由5000改为3000,且在端口与API路径之间加上/api1
)
如果get的端口是发送端口可以将'http://localhost:3000/api1/students'
简写为'/api1/students'
axios.get('/api1/students').then(
response => {
console.log('成功了',response.data);},
error => {
console.log('失败了',error);}
)
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
成品效果图:
教程流程视频(建议初学者看看):[P67-P73]
(该部分理解后,可以以后需要的时候回来复制即可)
public/index.html
<html> <head> <meta charset="UTF-8" /> <title>这是标题title> head> <body> <div id="root">div> body> html>
src/index.js
// 引入react核心库 import React from 'react' // 引入ReactDOM import ReactDOM from 'react-dom' // 引入App import App from './App' ReactDOM.render(<App/>,document.getElementById('root'))
src/App.jsx
import React, { Component } from 'react' import Search from './components/Search' import List from './components/List' export default class App extends Component { state = { // 初始化状态 users:[], // users初始值为数组 isFirst:true, // 是否为第一次打开页面 isLoading:false, // 标识是否处于加载中 err:'', // 存储请求相关的错误信息 } // 更新App的state updateAppState = (stateObj)=>{ this.setState(stateObj) } render() { return ( <div className="container"> <Search updateAppState={ this.updateAppState}/> <List { ...this.state}/> </div> ) } }
src/components/Search/index.jsx
import React, { Component } from 'react' import axios from 'axios' export default class Search extends Component { search = ()=>{ // 获取用户的输入(连续解构赋值+重命名) const { keyWordElement:{ value:keyWord}} = this // 发送请求前通知App更新状态 this.props.updateAppState({ isFirst:false,isLoading:true}) // 发送网络请求 axios.get(`https://api.github.com/search/users?q=${ keyWord}`).then( response => { // 请求成功后通知App更新状态(调用父组件函数把参数传回父组件) this.props.updateAppState({ isLoading:false,users:response.data.items}) }, error => { // 请求失败后通知App更新状态(调用父组件函数把参数传回父组件) this.props.updateAppState({ isLoading:false,err:error.message}) } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索github用户</h3> <div> <input ref={ c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={ this.search}>搜索</button> </div> </section> ) } }
src/components/List/index.jsx
import React, { Component } from 'react' import './index.css' export default class List extends Component { render() { const { users,isFirst,isLoading,err} = this.props return ( <div className="row"> { isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> : isLoading ? <h2>Loading......</h2> : err ? <h2 style={ { color:'red'}}>{ err}</h2> : users.map((userObj)=>{ return ( <div key={ userObj.id} className="card"> <a rel="noreferrer" href={ userObj.html_url} target="_blank"> <img alt="head_portrait" src={ userObj.avatar_url} style={ { width:'100px'}}/> </a> <p className="card-text">{ userObj.login}</p> </div> ) }) } </div> ) } }
src/components/List/index.css
.album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; }
[GitHub链接]
命令行中输入:npm install pubsub-js --save
(1)引入库:import PubSub from 'pubsub-js'
(2)订阅消息:var messageId = PubSub.subscribe('messageName', function(data){ });
(3)发布消息:PubSub.publish('messageName', data)
(4)取消订阅:PubSub.unsubscribe(messageId);
仅列出修改的文件(src/App.jsx、src/components/Search/index.jsx、src/components/List/index.jsx)
src/App.jsx
import React, { Component } from 'react' import Search from './components/Search' import List from './components/List' export default class App extends Component { render() { return ( <div className="container"> <Search/> <List/> </div> ) } }
src/components/Search/index.jsx
import React, { Component } from 'react' import PubSub from 'pubsub-js' import axios from 'axios' export default class Search extends Component { search = ()=>{ // 获取用户的输入(连续解构赋值+重命名) const { keyWordElement:{ value:keyWord}} = this // 发送请求前通知List更新状态 PubSub.publish('updateData',{ isFirst:false,isLoading:true}) // 发送网络请求 axios.get(`https://api.github.com/search/users?q=${ keyWord}`).then( response => { // 请求成功后通知List更新状态 PubSub.publish('updateData',{ isLoading:false,users:response.data.items}) }, error => { // 请求失败后通知App更新状态 PubSub.publish('updateData',{ isLoading:false,err:error.message}) } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索github用户</h3> <div> <input ref={ c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={ this.search}>搜索</button> </div> </section> ) } }
src/components/List/index.jsx
import React, { Component } from 'react' import PubSub from 'pubsub-js' import './index.css' export default class List extends Component { state = { // 初始化状态 users:[], // users初始值为数组 isFirst:true, // 是否为第一次打开页面 isLoading:false, // 标识是否处于加载中 err:'', // 存储请求相关的错误信息 } componentDidMount(){ this.token = PubSub.subscribe('updateData',(_,stateObj)=>{ this.setState(stateObj) }) } componentWillUnmount(){ PubSub.unsubscribe(this.token) } render() { const { users,isFirst,isLoading,err} = this.state return ( <div className="row"> { isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> : isLoading ? <h2>Loading......</h2> : err ? <h2 style={ { color:'red'}}>{ err}</h2> : users.map((userObj)=>{ return ( <div key={ userObj.id} className="card"> <a rel="noreferrer" href={ userObj.html_url} target="_blank"> <img alt="head_portrait" src={ userObj.avatar_url} style={ { width:'100px'}}/> </a> <p className="card-text">{ userObj.login}</p> </div> ) }) } </div> ) } }
[官方文档]
Fetch最主要的目的在于将步骤细化,将axios中使用get方法直接获取数据变为:先联系服务器,再获取数据。
(1)原生函数,不再使用XMLHttpRequest对象提交ajax请求
(2)老版本浏览器可能不支持
GET请求:
fetch(url).then(function(response) { return response.json() }).then(function(data) { console.log(data) }).catch(function(e) { console.log(e) });
POST请求:
fetch(url, { method: "POST", body: JSON.stringify(data), }).then(function(data) { console.log(data) }).catch(function(e) { console.log(e) })
axios版本:
axios.get(`https://api.github.com/search/users?q=${ keyWord}`).then( response => { // 请求成功后通知List更新状态 PubSub.publish('updateData',{ isLoading:false,users:response.data.items}) }, error => { // 请求失败后通知App更新状态 PubSub.publish('updateData',{ isLoading:false,err:error.message}) } )
Fetch版本(未优化):
fetch(`https://api.github.com/search/users?q=${ keyWord}`).then( response => { console.log('联系服务器成功了'); return response.json() }, error => { console.log('联系服务器失败了',error); return new Promise(()=>{ }) } ).then( response => { console.log('获取数据成功了',response); PubSub.publish('updateData',{ isLoading:false,users:data.items}) }, error => { console.log('获取数据失败了',error); PubSub.publish('updateData',{ isLoading:false,err:error.message}) } )
Fetch版本(优化):
search = async()=>{ ... try { const response= await fetch(`https://api.github.com/search/users?q=${ keyWord}`) const data = await response.json() console.log(data); PubSub.publish('updateData',{ isLoading:false,users:data.items}) } catch (error) { console.log('请求出错',error); PubSub.publish('updateData',{ isLoading:false,err:error.message}) } ... }
考虑例如带有网络请求的组件,要考虑请求失败怎么办。
let obj = {
a:{
b:1}}
const {
a} = obj; // 传统解构赋值
const {
a:{
b}} = obj; // 连续解构赋值
const {
a:{
b:value}} = obj; // 连续解构赋值+重命名
(1)先订阅,再发布(理解:有一种隔空对话的感觉)
(2)适用于任意组件间通信
(3)要在组件的componentWillUnmount中取消订阅
try {
const response= await fetch(`https://api.github.com/search/users?q=${
keyWord}`)
const data = await response.json()
console.log(data);
} catch (error) {
console.log('请求出错',error);
}