【React】React学习笔记2(React脚手架、组件通信与网络请求库)

React学习笔记2(React脚手架、组件通信与网络请求库)

  • 零 l  说明
  • 一 l  React 应用(基于React 脚手架)
    • (一)React 脚手架 create-react-app
      • 1、脚手架是什么
      • 2、create-react-app快速创建项目
      • 3、create-react-app创建的脚手架的项目结构
    • (二)项目规范
      • 1、最简单的index.html、index.js、App.js
      • 2、模块化
      • 3、功能界面的组件化编码流程(通用)
    • (三)案例实践:TodoList
      • 1、主要代码
      • 2、css代码
      • 3、总结部分(todoList案例相关知识点)
  • 二 l  React Ajex(请求并获取后端数据)
    • (一)简单介绍
    • (二)Axios的跨域问题
      • 1、配置package.json(路径:项目根目录/package.json)
      • 2、单独的代理配置文件
    • (三)案例实践:Github搜索
      • 1、主要代码
      • 2、css代码
    • (四)消息订阅与发布(兄弟组件间如何通信)
      • 1、工具库:PubSubJS
      • 2、下载命令
      • 3、使用方法(jsx代码中):
      • 4、优化“(三)案例实践:Github搜索”
    • (五)【扩展】Fetch
      • 1、介绍
      • 2、相关API
      • 3、示例
    • (六)总结(三、四、五)
      • 1、设计状态时要考虑全面
      • 2、ES6小知识点:解构赋值+重命名
      • 3、消息订阅与发布机制
      • 4、fetch发送请求(关注分离的设计思想)

零 l  说明


本文参考教程:[尚硅谷2021版React技术全家桶]

本文上接:[React学习笔记1(React概念)]






一 l  React 应用(基于React 脚手架)


(一)React 脚手架 create-react-app

1、脚手架是什么

  xxx脚手架可以快速搭建一个基于xxx库的模板项目(包含自定义配置、依赖管理和一个简单demo)
  脚手架特点:模块化、组件化、工程化

2、create-react-app快速创建项目

(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

3、create-react-app创建的脚手架的项目结构

|- 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库的支持)】


一些解释与补充:

  1. public文件夹中只有index.html一个.html文件,因为React是SPA(Single Page web Application单页web应用),只有一个页面,页面变化靠组件。(找不到资源时会显示此页面)
  2. 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. 项目启动时读取的文件顺序
    (1) src/index.js【读取入口文件(如果index.css有样式这一步也会读取)】
    (2) src/APP.js【获取根组件】
    (3) public/index.html【获取容器】

(二)项目规范

1、最简单的index.html、index.js、App.js

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

2、模块化

模块化步骤(规范写法):具体可以看下面的代码

  1. 将组件的后缀从.js改为.jsx(代码不用改)
  2. 在src下建立components文件夹,在components文件夹下建立组件文件夹(这里建立了Hello文件夹)
  3. 组件文件夹下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中的函数式组件模板

3、功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件
     (1) 动态显示初始化数据
       a. 数据类型
       b. 数据名称
       c. 保存在哪个组件中
     (2) 交互(从绑定事件监听开始)

(三)案例实践:TodoList

成品效果图:【React】React学习笔记2(React脚手架、组件通信与网络请求库)_第1张图片
教程流程视频(建议初学者看看):[P56]
(该部分理解后,可以以后需要的时候回来复制即可)


1、主要代码

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>
   )
 }
}

2、css代码

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

3、总结部分(todoList案例相关知识点)

  1. 拆分组件、实现静态组件,注意:className、style的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?
    (1) 某个组件使用:放在其自身的state中
    (2) 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信:
    (1) 【父组件】给【子组件】传递数据:通过props传递
    (2) 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
  5. 状态在哪里,操作状态的方法就在哪里






二 l  React Ajex(请求并获取后端数据)


  该部分所有示例的客户端端口都是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携带参数的几种方式

  1. query
  2. params
  3. body
    (1) urlencode
    (2) json

(二)Axios的跨域问题

  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(路径:项目根目录/package.json)

(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);}
)
  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

2、单独的代理配置文件

(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);}
)
  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

(三)案例实践:Github搜索

成品效果图:

教程流程视频(建议初学者看看):[P67-P73]
(该部分理解后,可以以后需要的时候回来复制即可)


1、主要代码

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="输入关键词点击搜索"/>&nbsp;
         <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>
   )
 }
}

2、css代码

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%;
}

(四)消息订阅与发布(兄弟组件间如何通信)

1、工具库:PubSubJS

  [GitHub链接]

2、下载命令

  命令行中输入:npm install pubsub-js --save

3、使用方法(jsx代码中):

  (1)引入库:import PubSub from 'pubsub-js'
  (2)订阅消息:var messageId = PubSub.subscribe('messageName', function(data){ });
  (3)发布消息:PubSub.publish('messageName', data)
  (4)取消订阅:PubSub.unsubscribe(messageId);

4、优化“(三)案例实践:Github搜索”

  仅列出修改的文件(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="输入关键词点击搜索"/>&nbsp;
         <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

  [官方文档]

1、介绍

  Fetch最主要的目的在于将步骤细化,将axios中使用get方法直接获取数据变为:先联系服务器,再获取数据。

(1)原生函数,不再使用XMLHttpRequest对象提交ajax请求
(2)老版本浏览器可能不支持

2、相关API

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)
})

3、示例

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})
 }
...
}

(六)总结(三、四、五)

1、设计状态时要考虑全面

  考虑例如带有网络请求的组件,要考虑请求失败怎么办。

2、ES6小知识点:解构赋值+重命名

let obj = {
     a:{
     b:1}}
const {
     a} = obj;  // 传统解构赋值
const {
     a:{
     b}} = obj;  // 连续解构赋值
const {
     a:{
     b:value}} = obj;  // 连续解构赋值+重命名

3、消息订阅与发布机制

(1)先订阅,再发布(理解:有一种隔空对话的感觉)
(2)适用于任意组件间通信
(3)要在组件的componentWillUnmount中取消订阅

4、fetch发送请求(关注分离的设计思想)

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);
}

你可能感兴趣的:(前端,reactjs,axios,fetch,PubSubJS)