React全家桶: github搜索案例_详细拆解

104 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

写在前面

在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。

那就让我们开始吧!

github搜索案例_静态组件

demo_users (2).gif

拆分组件

App组件、Search组件、List组件

静态页面拆分

  • 将静态页面全部复制到App组件,进行详细拆分。

对class、fontsize、style等关键字进行替换为className、fontSize、style={{}}。

  • 创建index.css文件,将全部css样式引入,在List组件中引入样式文件。实现样式加工。

  • 将对应的html拆分到子组件内部,在List组件中引入然后使用。

  • 引入bootstrap

①在public文件夹下面创建一个css文件添加bootstrap.css

②在index.html中引入bootstrap.css

<link rel="stylesheet" href="/css/bootstrap.css">
  • 创建component文件夹

在component文件夹下面创建不同的子组件文件夹。

添加rel="noreferrer"

1.超链接 target="_blank" 要增加 rel="nofollow noopener noreferrer" 来堵住钓鱼安全漏洞。如果你在链接上使用 target="_blank"属性,并且不加上rel="noopener"属性,那么你就让用户暴露在一个非常简单的钓鱼攻击之下。

// a标签下添加  *rel* = "noreferrer"

 <a *href*="https://github.com/reactjs" *target*="_blank"  *rel* = "noreferrer">

image.png

image.png

img标签

  • img图片 alt属性值最后不要使用photo,picture,img等等。脚手架检查会觉得不规范。

image.png

github搜索案例_axios发送请求

修改Search组件

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

书写点击事件,获取input输入值

  • 使用ref、onclick
search = ()=>{
    // 获取用户输入的值
      console.log("this.keyWordElement.value")
}

<input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
<button onClick={this.search}>搜索</button>
  • 解构赋值连续写法
//获取用户的输入(连续解构赋值+重命名)

const {keyWordElement:{value:keyWord}} = *this*

console.log(value) // === console.log(keyWord)

console.log(keyWordElement) // Error

 Axios请求

  1. 引入Axios
import axios from 'axios'
  1. 使用ref,onClick事件函数绑定input标签和button标签。

github 多次访问失效

  • 解决方式

开启一个端口号为5000的服务器。对github发送请求的时候,会通过该服务器进行请求。

在多次访问之后无法从github获取数据时,服务器会直接返回本身的数据到客户端。

  • 跨域

后端使用cors解决了跨域问题

  • 对端口号为5000的服务器配置代理
const proxy = require('http-proxy-middleware')

module.exports = function(app){
	app.use(
		proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
			target:'http://localhost:5000', //请求转发给谁
			changeOrigin:true,//控制服务器收到的请求头中Host的值
			pathRewrite:{'^/api1':''} //重写请求路径(必须)
		})
	)
}

请求地址为:http://localhost:3000/api1/search/users?q=${keyWord}

github搜索案例_展示数据

父组件初始化状态

  • 父组件App.js

使用父组件写方法和初始化status,子组件使用方法更新status的手段将获得的用户信息反馈到界面。

// 父组件App

state = {

        users:[]

    }

    saveUsers = (users)=> {

this.setState({users})

}

  • 子组件Search组件

调用函数改变状态

<Search updateAppState={this.updateAppState} />

search = ()=>{
        //获取用户的输入(连续解构赋值+重命名)
        const {keyWordElement:{value:keyWord}} = this
        //发送网络请求
        axios.get(`/api1/search/users?q=${keyWord}`).then(
                response => {
                        //请求成功后通知App更新状态
                        this.props.saveUsers(response.data.items);
                },
                error => {
                        //请求失败后通知App更新状态
                        this.props.updateAppState({isLoading:false,err:error.message})
                }
        )
}
        

子组件渲染

  • List组件
<List {...this.state}/>
return (
        <div className="row">
                {
                        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>
)

github搜索案例_完成案例

为了完善功能,第一次请求,加载中,加载失败等等更新status。

子组件Search调用了2次updateAppState方法,子组件List使用了嵌套三元符分别展示不同页面

初始化状态

  • 添加加载中、初次打开页面状态
state = { //初始化状态
        users:[], //users初始值为数组
        isFirst:true, //是否为第一次打开页面
        isLoading:false,//标识是否处于加载中
        err:'',//存储请求相关的错误信息
} 

  • 书写更新状态的函数 updateAppState
//更新App的state

    updateAppState = (stateObj)=>{

this.setState(stateObj)

}

  • 向Search组件传递 updateAppState
<Search updateAppState={this.updateAppState} />
  • 向List组件传递状态属性。
 <List {...this.state}/>

  • 子组件Search发送请求成功后调用updateAppState函数
//发送请求前通知App更新状态

this.props.updateAppState({isFirst:false,isLoading:true}) //一旦请求就开始改变
  • 请求成功后通知App更新状态
//子组件Search

//发送网络请求

axios.get(`/api1/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}) // 不能传递对象

    }

)
  • 不能传递对象
 this.props.updateAppState({isLoading:false,err:error.message}) // 不能传递对象

image.png

  • 根据不同状态时刻,展示不同页面

    1.初次打开页面

    2.加载中

    3.加载失败

    4.加载成功

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

消息订阅与发布技_pubsub

github搜索案例 修改升级

工具库: PubSubJS

下载: npm install pubsub-js --save

使用:

1import PubSub from 'pubsub-js' //引入

2PubSub.subscribe('delete', function(data){ }); //订阅

3PubSub.publish('delete', data) //发布消息

1.将App组件的状态更改到List组件

state = { //初始化状态
        users:[], //users初始值为数组
        isFirst:true, //是否为第一次打开页面
        isLoading:false,//标识是否处于加载中
        err:'',//存储请求相关的错误信息
} 

const {users,isFirst,isLoading,err} = this.state

2.将App的函数,状态全部清除

export default class App extends Component {
	render() {
		return (
			<div className="container">
				<Search/>
				<List/>
			</div>
		)
	}
}

3. 引入pubsub并且挂载组件之后订阅消息

  • List组件订阅消息
import PubSub from 'pubsub-js'

componentDidMount(){
        this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
                this.setState(stateObj)
        })
}

4.引入pubsub并且挂载组件之后发布消息

import PubSub from 'pubsub-js'

search = ()=>{
        //获取用户的输入(连续解构赋值+重命名)
        const {keyWordElement:{value:keyWord}} = this
        //发送请求前通知List更新状态
        PubSub.publish('atguigu',{isFirst:false,isLoading:true})
        //发送网络请求
        axios.get(`/api1/search/users?q=${keyWord}`).then(
                response => {
                        //请求成功后通知List更新状态
                        PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
                },
                error => {
                        //请求失败后通知App更新状态
                        PubSub.publish('atguigu',{isLoading:false,err:error.message})
                }
        )
}

image.png

组件将要卸载取消订阅消息

componentWillUnmount(){
        PubSub.unsubscribe(this.token)
}

fetch发送请求

发送AJAX请求方式

  1. xhr对象

传送门:www.html.cn/doc/ajax/xm…

2.jQuery(对象xhr封装,使用回调)

传送门:jquery.com/

优点:封装了请求

缺点:产生回调地狱

3.Axios(,对象xhr封装,promise风格)

Fetch

1.window对象自带
2.不用下载,浏览器中直接使用
3.promise风格

由于一些不可抗力因素,比如断网

image.png

  • response.json

返回一个Promise实例对象。

1.获取数据成功

这个Promise对象保存获取的数据

  1. 获取数据失败

这个Promise对象保存了失败的原因

image.png

image.png

.then 链式调用

  • .then 的返回值就是一个Promise实例对象,可以继续调用 .then。
//发送网络请求---使用fetch发送(未优化)
 fetch(`/api1/search/users2?q=${keyWord}`).then(
        response => {
                console.log('联系服务器成功了');
                return response.json()
        },
        error => {
                console.log('联系服务器失败了',error);
                return new Promise(()=>{})
        }
).then(
        response => {console.log('获取数据成功了',response);},
    error => {console.log('获取数据失败了',error);}
  • 发送网络请求---使用fetch发送(优化)
//发送网络请求---使用fetch发送(优化)
try {
        const response= await fetch(`/api1/search/users2?q=${keyWord}`)
        const data = await response.json()
        console.log(data);
        PubSub.publish('atguigu',{isLoading:false,users:data.items})
} catch (error) {
        console.log('请求出错',error);
        PubSub.publish('atguigu',{isLoading:false,err:error.message})
}

fetch特点

1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求

2. 老版本浏览器可能不支持

3. 关注分类,只要联系上了服务器就会显示访问成功,而不管是404还是什么(路径有问题)。断网等等不可抗力因数会到时联系服务器失败。

4. Respone原生对象上又一个json方法。