一、项目效果图
项目思路:可以将如下页面划分为Search、List两个组件,App组件用来组合两个组件。
项目难点:跨域、Bootstrap框架引用、axios请求
项目注意点:设计状态时考虑要全面,比如List组件必须考虑到网络请求成功、失败等情况,还有个用来显示loading的信息等情况。
二、编写各个组件,明确各个组件返回的html以及各个组件的css样式
1.项目引入自编css
放在src/components目录下,各个组件的文件夹内;组件内使用时直接import即可;记得需要加后缀.css。只有后缀js和jsx不需要写。
import './App.css'
2.项目引入第三方css文件
(1)放在public/css目录下;使用时在index.html 通过link引入
相对当前路径 <link src="./css/bootstrap.css"/> 相对根路径 react项目根路径为public文件夹 常用 <link src="/css/bootstrap.css"/> 绝对路径 只有react项目才可以识别 <link src="%PUBLIC_URL%/css/bootstrap.css"/>
(2)npm或者yarn安装css库,然后import导入
1)可以在react项目中执行以下命令安装bootstrap npm install bootstrap@3 --save 2)使用bootstrap的组件中导入组件库和css样式库 import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/js/bootstrap'; 3)因为bootstrap依赖jQuery,在react的index.html文件中加个jQuery的引入 <script src="https://code.jquery.com/jquery-3.6.0.js"></script>
三、动态获取数据,渲染各个组件
1.解构赋值
解构语法为: const {属性名} = 对象
(1)多层对象的连续解构赋值 let obj = {a:{b:{c:"Hello"}}} const {a:{b}:{c}} = obj //输出c值为 Hello console.log(a) //a 或者 b没有值 连续解构赋值且对属性重命名,比如将上面的c重命名为data const {a:{b}:{c:data}} = obj console.log(data) 此时c属性重命名为data (2)单层对象的解构赋值 let obj = {a:'hello',b:'world'} const {a,b} = obj //将对象obj的属性全部赋值给a b
2.axios发送个体请求
(1)安装axios npm i axios (2)发送请求 注意get中url为反引号 import axios from 'axios' axios.get(`http://ip:port/getUserName/${userId}`).then( response=>{console.log('输出成功了',response.data)}, error=>{console.log('失败了',error)} )
3.JSX判断语句
JSX不允许使用if语句,可以连续使用三目运算符来实现判断
a?b:c?d:f 依次判断来决定值是哪个
export default class List extends Component {
render() {
const { users, isFirst, isError, isLoading, err } = this.props
return (
<div className="row">
{
isFirst ? < h2 > 请输入名字进行查询</h2> :
isLoading ? < h2 > Loading....</h2 > :
isError ? err :
users.map(
user => {
return (
<div key={user.id} className="card">
<a href={user.url} target="_blank" rel="noreferrer" >
<img alt='替代图片' src={user.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{user.login}</p>
</div>)
}
)
}
</div>
)
}
}
4.项目数据传递思路
axios在Search组件发送数据,获得数据通过调用父组件App通过props传递过来的函数更新App组件的state,然后App通过props将state传给List组件。相当于实现了父子组件传递、子父组件传递数据。问题是,Search组件直接将数据传给List组件不好吗?为甚要借助父组件作为中间进行数据传递呢?
四、消息订阅-发布机制
(1)作用:用来实现任意组件之间的数据传递。 以前学习的都是通过props实现父子组件传递。
(2)使用步骤
- 工具库 PubSubJS 。可在github中搜索查看具体用法。
- 下载 npm install pubsub-js --save
- 使用
componentDidMount中订阅消息,然后获取数据后调用组件setState方法重新渲染组件;需要发布消息的地方发布消息。
注:一般在组件的componentDidMount中订阅消息,在componentWillUnMount中取消订阅;npm 库中包名规范中都是小写的,所以引入时是pubsub-js 小写,不是PubSubJS
import PubSub from 'pubsub-js' //引入 //subscribe接收两个参数:topic和回调函数 回调函数接收两个参数msg和数据,其中msg记录了topic,很多情况下没必要使用。 const token = PubSub.subscribe('delete', function(msg,data){ }); //订阅 PubSub.publish('delete', data) //发布消息 PubSub.unsubscribe(token) //取消订阅
注:subscribe回调函数必须接收两个参数,如果msg不使用,那么可以通过 _ 占位符来去掉msg。
const token = PubSub.subscribe('delete', function(_,data){ }); //订阅
如下实习Search、List兄弟组件的通信
Search组件
import React, { Component } from 'react' import PubSub from 'pubsub-js' //引入 import axios from 'axios' export default class Search extends Component { searchUsers = () => { const { keyWrodEle: { value: keyWorld } } = this PubSub.publish("userTopic", { isLoading: true, isFirst: false }) axios.get(`https://api.github.com/search/users?q=${keyWorld}`).then( rsp => { console.log('成功了', rsp.data) PubSub.publish("userTopic", { isLoading: false, isError: false, users: rsp.data.items, err: '' }) }, error => { console.log('失败报错', error); PubSub.publish("userTopic", { isLoading: false, isError: true, err: error }) } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">Search Github Users</h3> <div> <input ref={c => this.keyWrodEle = c} type="text" placeholder="enter the name you search" /> <button onClick={this.searchUsers}>Search</button> </div> </section> ) } }
List组件
import React, { Component } from 'react' import PubSub from 'pubsub-js' //引入 import './index.css' export default class List extends Component { state = { users: [], isFirst: true, err: '', isLoading: false, isError: false } componentDidMount() { PubSub.subscribe("userTopic", (_, data) => { this.setState(data) }) } render() { const { users, isFirst, isError, isLoading, err } = this.state return ( <div className="row"> { isFirst ? < h2 > 请输入名字进行查询</h2> : isLoading ? < h2 > Loading....</h2 > : isError ? err : users.map( user => { return ( <div key={user.id} className="card"> <a href={user.url} target="_blank" rel="noreferrer" > <img alt='替代图片' src={user.avatar_url} style={{ width: '100px' }} /> </a> <p className="card-text">{user.login}</p> </div>) } ) } </div> ) } }
App组件 用来组合组件
import React, { Component } from 'react' import Search from './components/Search' import List from './components/List' import './App.css' export default class App extends Component { render() { return ( <div className="container"> <Search /> <List /> </div> ) } }
五、扩展:前端发送网络请求
前端发送网络请求有XMLHttpRequest和fetch两种JS原生支持方式。ajax、jquery、axios都是基于XMLHttpRequest来扩展的。axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。
fetch发送请求
因为是JS原生支持,不需要引入既可以使用。但是fetch是关注分离的设计模式,所以第一个then的rsp表示是否握手服务器成功;rsp.json()返回服务器请求的数据,是一个promise对象。然后第二个then表示获取到的数据 。catch对错误统一处理。
fetch对老版本浏览器兼容不好,故不怎么使用。
fetch(`https://api.github.com/search/users?q=${keyWorld}`).then( rsp => { console.log('服务器握手成功了') return rsp.json() }, ).then( rsp => { console.log("获取数据成功", rsp) }, ).catch( err => { console.log("失败", err); } )
fetch结合await发送请求
searchUsers = async() => {
try {
const rsp = await fetch(`https://api.github.com/search/users?q=${keyWorld}`)
const data = await rsp.json()
console.log("成功收到数据", data)
} catch (error) {
console.log('错误', error)
}
}
//#region //#endregion 这个语法可以将代码整体收缩起来。
//#region fetch发送网络请求 未优化
...代码段
//#endregion