React 全栈体系(八)

第四章 React ajax

三、案例 – github 用户搜索

2. 代码实现

2.3 axios 发送请求
Search
/* 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
    console.log(keyword);
    //发送网络请求
    axios.get(`https://api.github.com/search/users?q=${keyword}`).then(
      response => {console.log('c', response.data);},
      error => {console.log('d', error);}
    )
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input ref={c => this.keyWordElement = c} type="text" placeholder="enter the name you search" />
          &nbsp;
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    );
  }
}
2.4 展示数据
2.4.1 App
/* 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初始值为数组

  saveUsers = (users)=>{
    this.setState({users})
  }
  render() {
    const {users} = this.state
    return (
      <div className="container">
        <Search saveUsers={this.saveUsers}/>
        <List users={users}/>
      </div>
    );
  }
}
2.4.2 Search
/* 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
    //发送网络请求
    axios.get(`https://api.github.com/search/users?q=${keyword}`).then(
      response => {
        this.props.saveUsers(response.data.items)
      },
      error => {console.log('d', error);}
    )
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input ref={c => this.keyWordElement = c} type="text" placeholder="enter the name you search" />
          &nbsp;
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    );
  }
}
2.4.3 List
/* src/components/List/index.jsx */
import React, { Component } from "react";
import "./index.css";

export default class List extends Component {
  render() {
    return (
      <div className="row">
        {this.props.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.5 完成案例
2.5.1 App
/* 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() {
    const { users } = this.state;
    return (
      <div className="container">
        <Search updateAppState={this.updateAppState} />
        <List {...this.state} />
      </div>
    );
  }
}
2.5.2 Search
/* 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">Search Github Users</h3>
        <div>
          <input
            ref={(c) => (this.keyWordElement = c)}
            type="text"
            placeholder="enter the name you search"
          />
          &nbsp;
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    );
  }
}
2.5.3 List
/* 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>Welcome, enter a keyword and then click search!</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>
    );
  }
}

四、消息订阅-发布机制

1. 工具库

  • PubSubJS

2. 下载

  • npm install pubsub-js --save

3. 使用

  • import PubSub from ‘pubsub-js’ //引入
  • PubSub.subscribe(‘delete’, function(data){ }); //订阅
  • PubSub.publish(‘delete’, data) //发布消息

4. github 用户搜索代码重构

4.1 App
/* 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>
    );
  }
}
4.2 Search
/* 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("alex", { isFirst: false, isLoading: true });
    //发送网络请求
    axios.get(`https://api.github.com/search/users?q=${keyword}`).then(
      (response) => {
        //请求成功后通知List更新状态
        PubSub.publish("alex", {
          isLoading: false,
          users: response.data.items,
        });
      },
      (error) => {
        //请求失败后通知List更新状态
        PubSub.publish("alex", { isLoading: false, err: error.message });
      }
    );
  };

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input
            ref={(c) => (this.keyWordElement = c)}
            type="text"
            placeholder="enter the name you search"
          />
          &nbsp;
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    );
  }
}
4.3 List
/* 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("alex", (_, stateObj) => {
      this.setState(stateObj);
    });
  }

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

  render() {
    const { users, isFirst, isLoading, err } = this.state;
    return (
      <div className="row">
        {isFirst ? (
          <h2>Welcome, enter a keyword and then click search!</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. 文档

  • https://github.github.io/fetch/

2. 特点

  • fetch: 原生函数,不再使用 XmlHttpRequest 对象提交 ajax 请求
  • 老版本浏览器可能不支持

3. 相关 API

3.1 GET 请求
fetch(url).then(function(response) {
    return response.json()
  }).then(function(data) {
    console.log(data)
  }).catch(function(e) {
    console.log(e)
  });
3.2 POST 请求
fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
}).then(function(data) {
  console.log(data)
}).catch(function(e) {
  console.log(e)
})

4. github 用户搜索代码 - fetch

Search
/* src/components/Search/index.jsx */
import React, { Component } from "react";
import PubSub from "pubsub-js";

export default class Search extends Component {
  search = async()=>{
		//获取用户的输入(连续解构赋值+重命名)
		const {keyWordElement:{value:keyWord}} = this
		//发送请求前通知List更新状态
		PubSub.publish('alex',{isFirst:false,isLoading:true})
		//#region 发送网络请求---使用axios发送
		/* axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
			response => {
				//请求成功后通知List更新状态
				PubSub.publish('alex',{isLoading:false,users:response.data.items})
			},
			error => {
				//请求失败后通知App更新状态
				PubSub.publish('alex',{isLoading:false,err:error.message})
			}
		) */
		//#endregion

		//发送网络请求---使用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);},
			error => {console.log('获取数据失败了',error);}
		) */

		//发送网络请求---使用fetch发送(优化)
		try {
			const response= await fetch(`https://api.github.com/search/users?q=${keyWord}`)
			const data = await response.json()
			PubSub.publish('alex',{isLoading:false,users:data.items})
		} catch (error) {
			PubSub.publish('alex',{isLoading:false,err:error.message})
		}
	}

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input
            ref={(c) => (this.keyWordElement = c)}
            type="text"
            placeholder="enter the name you search"
          />
          &nbsp;
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    );
  }
}

六、总结

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(`/api1/search/users2?q=${keyWord}`)
		const data = await response.json()
		console.log(data);
	} catch (error) {
		console.log('请求出错',error);
	}