使用create-react-app创建react应用
react脚手架
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查、jsx编译、devServer...)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
- react提供了一个用于创建react项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
创建项目并启动
第一步 ,全局安装:npm i -g create-react-app
第二步 ,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步 ,进入项目文件夹:cd hello-react
第四步 ,启动项目:npm start
react脚手架项目结构
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库的支持)
功能界面的组件化编码流程(通用)
-
拆分组件: 拆分界面,抽取组件
-
实现静态组件: 使用组件实现静态页面效果
-
实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
组件的组合使用 -TodoList
功能 : 组件化实现此功能
1. 显示所有 todo 列表
2. 输入文本 , 点击按钮显示到列表的首位 , 并清除输入的文本
相关代码:
App.jsx
javascript
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:true},
]}
//addTodo用于添加一个todo,接收的参数是一个todo对象
addTodo = (todoObj) =>{
// console.log('App',todoObj);
//获取原todos
const {todos} = this.state
//追加一个todo
const newTodos = [todoObj,...todos]
//更新状态
this.setState({
todos:newTodos
})
}
//updateTodo用于更新一个todo,接收的参数是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">
{/* 通过props将函数传递给子组件,子组件在适当的时机调用这个函数,并将参数交给App,然后App将这个参数放入自己的状态里,就会引起App状态的更改,然后调用App里面的render,由于List是App的子组件,就会引发子组件的重新渲染 */}
<Header addTodo={this.addTodo} />
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />
</div>
</div>
);
}
}
App.css
javascript
/*base*/
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;
}
Header文件
index.jsx
javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'
export default class index 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
}
console.log(target.value);
//准备好一个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>
)
}
}
index.css
javascript
/*header*/
.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);
}
List文件
index.jsx
javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
export default class index extends Component {
//对接收的props进行类型和必要性的限制
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodo: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired,
}
render() {
const {todos,updateTodo,deleteTodo} = this.props
return (
<ul className="todo-main">
{
todos.map(todo=>{
// return <Item key={todo.id} id={todo.id} name={todo.name} done={todo.done} />
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo} />
})
}
</ul>
)
}
}
index.css
javascript
/*main*/
.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;
}
Item文件
index.jsx
javascript
import React, { Component } from 'react'
import './index.css'
export default class index extends Component {
state = {mouse:false} //标识鼠标移入、移出
//标识鼠标移入、移出的回调
handleMouse=(flag)=>{
return ()=>{
this.setState({
mouse:flag
})
}
}
//勾选、取消勾选某一个todo的回调
handleCheck = (id) =>{
return (event)=>{
console.log(id,event.target.checked);
this.props.updateTodo(id,event.target.checked)
}
}
//删除一个todo的回调
handleDelete = (id) =>{
console.log('通知App删除',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'}} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
<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>
)
}
}
index.css
javascript
/*item*/
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;
}
Footer文件
index.jsx
javascript
import React, { Component } from 'react'
import './index.css'
export default class index extends Component {
//全选checkbox的回调
handleCheckedAll=(event)=>{
this.props.checkAllTodo(event.target.checked)
}
//清除已完成任务的回调
handleClearAllDone = () => {
this.props.clearAllDone()
}
render() {
const {todos} = this.props
//已完成的个数
//pre 上一次的返回值,0 初始值,current当前的todo对象
//第一次调用的时候没有上一次,所以pre初始是0
const doneCount = todos.reduce((pre,todo)=>pre + (todo.done ? 1 : 0),0)
console.log(doneCount);
//总数
const total = todos.length
return (
<div className="todo-footer">
<label>
{/* defaultChecked只在第一次生效 */}
<input type="checkbox" onChange={this.handleCheckedAll} checked={doneCount === total && total!==0 ? true : false} />
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
</div>
)
}
}
index.css
javascript
/*footer*/
.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;
}
React ajax
理解
前置说明
- React本身只关注于界面, 并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react应用中需要集成第三方ajax库(或自己封装)
常用的ajax请求库
- jQuery: 比较重, 如果需要另外引入不建议使用
- axios: 轻量级, 建议使用
1.- 封装XmlHttpRequest对象的ajax
- promise风格
- 可以用在浏览器端和node服务器端
App.jsx
javascript
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);}
)
}
geCarData = () =>{
axios.get('http://localhost:3000/api2/cars').then(
response=>{console.log('成功了',response.data);},
error=>{console.log('失败了',error);}
)
}
render() {
return (
<div>
<button onClick={this.getStudentData}>点我获取学生数据</button>
<button onClick={this.geCarData}>点我获取汽车数据</button>
</div>
)
}
}
setupProxy.js
javascript
// const proxy = require("http-proxy-middleware");
// module.exports = function (app) {
// app.use(
// proxy("/api1", {
// //遇见/api1前缀的请求,就会触发该代理配置
// target: "http://localhost:5000", //请求转发给谁
// changeOrigin: true, //控制服务器收到的请求头中Host字段的值
// pathRewrite: { "^/api1": "" }, //重写请求路径(必须)
// }),
// proxy("/api2", {
// target: "http://localhost:5001",
// changeOrigin: true,
// pathRewrite: { "^/api2": "" },
// })
// );
// };
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api1",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: { "^/api1": "" },
})
);
app.use(
"/api2",
createProxyMiddleware({
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/api2": "" },
})
);
};
笔记:
javascript
# react 脚手架配置代理总结
## 方法一
> 在 package.json 中追加如下配置
```json
"proxy":"http://localhost:5000"
```
说明:
1. 优点:配置简单,前端请求资源时可以不加任何前缀。
2. 缺点:不能配置多个代理。
3. 工作方式:上述方式配置代理,当请求了 3000 不存在的资源时(先去本地路径获取),那么该请求会转发给 5000 (优先匹配前端资源)
## 方法二
1. 第一步:创建代理配置文件
```
在src下创建配置文件:src/setupProxy.js
```
2. 编写 setupProxy.js 配置具体代理规则:
```js
const proxy = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
proxy("/api1", {
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy("/api2", {
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/api2": "" },
})
);
};
```
说明:
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
axios
文档
GitHub - axios/axios: Promise based HTTP client for the browser and node.js
相关 API
1.GET请求
javascript
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
2.POST请求
javascript
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
案例--- github 用户搜索
请求地址: https://api.github.com/search/users?q=xxxxxx
代码:
github搜索案例_axios:
App.jsx
javascript
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
import './App.css'
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>
)
}
}
setupProxy.js
javascript
//这种写法会导致localhost拒绝访问的问题
// const proxy = require("http-proxy-middleware");
// module.exports = function (app) {
// app.use(
// proxy("/api1", {
// //遇见/api1前缀的请求,就会触发该代理配置
// target: "http://localhost:5000", //请求转发给谁
// changeOrigin: true, //控制服务器收到的请求头中Host字段的值
// pathRewrite: { "^/api1": "" }, //重写请求路径(必须)
// })
// );
// };
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api1",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: { "^/api1": "" },
})
);
};
components
Search/index.jsx:
javascript
import React, { Component } from 'react'
import axios from 'axios'
export default class index extends Component {
search = () =>{
//获取用户输入(连续解构赋值+重命名)
// console.log(this.keyWorldElement.value);
//keyWorldElement没有被定义,{value:keyword}解构出来之后可以重新赋值
const {keyWorldElement:{value:keyword}} = this
console.log(keyword);
//发送请求前通知App要更新状态
this.props.updateAppState({isFirst:false,isLoading:true});
//发送网络请求 后端解决跨域:cors
// http://localhost:3000可以省略
axios.get(`/api1/search/users?q=${keyword}`).then(
response=>{
// console.log('成功了', response.data);
//请求成功后通知App更新状态
this.props.updateAppState({isLoading:false,users:response.data.items})
},
error=>{
console.log('失败了', 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.keyWorldElement = c} type="text" placeholder="输入关键词点击搜索"/>
<button onClick={this.search}>搜索</button>
</div>
</section>
)
}
}
List/index.jsx
javascript
import React, { Component } from 'react'
import './index.css'
export default class index 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="https://github.com/reactjs" target="_blank">
<img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div>
)
}
}
List/index.css
javascript
.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%;
}
消息订阅-发布机制
- 工具库: PubSubJS
- 下载: npm install pubsub-js --save
- 使用:
1.import PubSub from 'pubsub-js' //引入
2.PubSub.subscribe('delete', function(data){ }); //订阅
3.PubSub.publish('delete', data) //发布消息

github搜索案例_pubsub:
App.jsx
javascript
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
import './App.css'
export default class App extends Component {
//更新App的state
updateAppState = (stateObj) =>{
this.setState(stateObj)
}
render() {
return (
<div className="container">
<Search updateAppState={this.updateAppState}/>
<List {...this.state}/>
</div>
)
}
}
Search/index.jsx
javascript
import React, { Component } from 'react'
import axios from 'axios'
import PubSub from 'pubsub-js'
export default class index extends Component {
search = () =>{
//发布的消息,就像是经常需要改变的数据,而其他组件需要根据这个会动态改变的数据来进行动态展示
console.log('Search组件发布消息了')
//获取用户输入(连续解构赋值+}重命名)
console.log(this.keyWorldElement.value);
//keyWorldElement没有被定义,{value:keyword}解构出来之后可以重新赋值
const {keyWorldElement:{value:keyword}} = this
console.log(keyword);
//发送请求前通知List要更新状态
//谁发送数据就在什么地方发送消息
PubSub.publish('atguigu',{isFirst:false,isLoading:true})
//发送网络请求 后端解决跨域:cors
axios.get(`/api1/search/users?q=${keyword}`).then(
response=>{
console.log('成功了', response.data);
//请求成功后通知List更新状态
PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
},
error=>{
console.log('失败了', error);
//请求失败后通知App更新状态
PubSub.publish('atguigu',{isLoading:false,err:error.message})
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">搜索Github用户</h3>
<div>
<input ref={c => this.keyWorldElement = c} type="text" placeholder="输入关键词点击搜索"/>
<button onClick={this.search}>搜索</button>
</div>
</section>
)
}
}
List/index.jsx
javascript
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
export default class index extends Component {
state = { //初始化状态
users: [], //users初始值为数组
isFirst: true, //是否为第一次打开页面
isLoading: false, //标识是否处于加载中
err: '',//存储请求相关的错误信息
}
componentDidMount(){
//谁接收数据就在什么地方订阅消息
//只要写了这个订阅消息之后,一旦有人发布这个消息就会收到数据
this.token = PubSub.subscribe('atguigu',(_, stateObj)=>{
// console.log('List组件收到数据了',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="https://github.com/reactjs" target="_blank">
<img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div>
)
}
}
扩展:Fetch
文档
2.https://segmentfault.com/a/1190000003810652
特点
- fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
- 老版本浏览器可能不支持
相关API
- GET请求
javascript
fetch(url).then(function(response) {
return response.json()
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
});
2.POST请求
javascript
fetch(url, {
method: "POST",
body: JSON.stringify(data),
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
})
github搜索案例_fetch:
App.jsx
javascript
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
import './App.css'
export default class App extends Component {
//更新App的state
updateAppState = (stateObj) =>{
this.setState(stateObj)
}
render() {
return (
<div className="container">
<Search updateAppState={this.updateAppState}/>
<List {...this.state}/>
</div>
)
}
}
Search/index.jsx
javascript
import React, { Component } from "react";
// import axios from 'axios'
import PubSub from "pubsub-js";
export default class index extends Component {
search = async () => {
//发布的消息,就像是经常需要改变的数据,而其他组件需要根据这个会动态改变的数据来进行动态展示
console.log("Search组件发布消息了");
//获取用户输入(连续解构赋值+}重命名)
console.log(this.keyWorldElement.value);
//keyWorldElement没有被定义,{value:keyword}解构出来之后可以重新赋值
const {
keyWorldElement: { value: keyword },
} = this;
console.log(keyword);
//发送请求前通知List要更新状态
//谁发送数据就在什么地方发送消息
PubSub.publish("atguigu", { isFirst: false, isLoading: true });
// #region 发送网络请求------------使用axios发送
//发送网络请求 后端解决跨域:cors
// axios.get(`/api1/search/users2?q=${keyword}`).then(
// response=>{
// console.log('成功了', response.data);
// //请求成功后通知List更新状态
// PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
// },
// error=>{
// console.log('失败了', error);
// //请求失败后通知App更新状态
// PubSub.publish('atguigu',{isLoading:false,err:error.message})
// }
// )
//#endregion
//发送网络请求------------使用fetch发送(未优化)
// fetch(`/api1/search/users2?q=${keyword}`).then(
// //能得到状态码是因为服务器告诉的,所以不管路径是否错误,只要服务器告诉状态码了,最后都是连接服务器成功了
// //成功请求后,获取数据成功后,这个调用response上面的json()方法,得到一个promise实例
// //如果联系服务器成功了,获取数据营业成功了,那么这个promise的状态也就变成了"fulfilled",而且里面保存着需要的数据
// //如果连接服务器成功了,但是获取数据失败了,那么这个promise的状态也就是失败的状态,里面保存着失败的原因
// response => {
// console.log('联系服务器成功了');
// return response.json()
// },
// //断网的时候,出现错误
// error=>{
// console.log('联系服务器失败了',error);
// 如果服务器都没有连接上,最好不好展示获取数据是成功还是失败,而是直接中断promise链
// return一个初始化状态的promise
// return new Promise(()=>{})
// }
// ).then(
// response=>{
// console.log('获取数据成功了', response);
// },
// error=>{
// console.log('获取数据失败了',error);
// }
// )
//发送网络请求------------使用fetch发送(优化)
// fetch(`/api1/search/users2?q=${keyword}`).then(
// response => {
// console.log('联系服务器成功了');
// return response.json()
// },
// ).then(
// response=>{
// console.log('获取数据成功了', response);
// },
// ).catch(
// 统一处理错误
// error=>{console.log('请求出错', error);}
// )
try {
const response = await fetch(`/api1/search/users2?q=${keyword}`);
const data = await response.json();
PubSub.publish('atguigu',{isLoading:false,users:data.items})
} catch (error) {
console.log("请求出错", error);
PubSub.publish('atguigu',{isLoading:false,users:error.message})
}
};
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">搜索Github用户</h3>
<div>
<input
ref={(c) => (this.keyWorldElement = c)}
type="text"
placeholder="输入关键词点击搜索"
/>
<button onClick={this.search}>搜索</button>
</div>
</section>
);
}
}
// const xhr = new XMLHttpRequest()
// xhr.open()
// xhr.send()
// ...
// jQuery将这些方法封装好了
// $(_,_)就可以直接发送请求
// 有一个问题,可能会产生回调地狱,jQuery发送ajax请求都靠回调函数来进行沟通
//成功就调用成功的回调,失败调用失败的回调
//如果是这样的需求,第一次成功了,在发第二次,第二次成了,再发第三次,第三次成了,再发第四次....很容易形成回调地域
//axios回调地域能解决,是promise风格的
// jQuery与axios都是对xhr的封装,他们都需要下载引入,都是第三方的对xhr的封装
// fetch也能发送请求,不用xhr,不是一个库,内置就有,window上面就有,fetch与xhr是并列的,
// 本身就是promise风格的
//优势:不是第三方库,不需要下载,直接使用,有浏览器就能使用
List/index.jsx
javascript
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
export default class index extends Component {
state = { //初始化状态
users: [], //users初始值为数组
isFirst: true, //是否为第一次打开页面
isLoading: false, //标识是否处于加载中
err: '',//存储请求相关的错误信息
}
componentDidMount(){
//谁接收数据就在什么地方订阅消息
//只要写了这个订阅消息之后,一旦有人发布这个消息就会收到数据
this.token = PubSub.subscribe('atguigu',(_, stateObj)=>{
// console.log('List组件收到数据了',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="https://github.com/reactjs" target="_blank">
<img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div>
)
}
}