关于javascript:在react js中进行API调用的正确方法是什么?

what is right way to do API call in react js?

我最近从Angular转到了ReactJs。 我正在使用jQuery进行API调用。 我有一个API,可返回要打印在列表中的随机用户列表。

我不确定如何编写我的API调用。 最佳做法是什么?

我尝试了以下操作,但未得到任何输出。 如果需要,我愿意实现替代API库。

下面是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';

export default class UserList extends React.Component {    
  constructor(props) {
    super(props);
    this.state = {
      person: []
    };
  }

  UserList(){
    return $.getJSON('https://randomuser.me/api/')
    .then(function(data) {
      return data.results;
    });
  }

  render() {
    this.UserList().then(function(res){
      this.state = {person: res};
    });
    return (
     
       
          {this.state.person.map((item, i) =>{
            return(
              {item.name.first}
              <span>{item.cell}, {item.email}</span>
            )
          })}
       
     
    )
  }
}


在这种情况下,您可以在componentDidMount内部进行ajax调用,然后更新state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export default class UserList extends React.Component {
  constructor(props) {
    super(props);

    this.state = {person: []};
  }

  componentDidMount() {
    this.UserList();
  }

  UserList() {
    $.getJSON('https://randomuser.me/api/')
      .then(({ results }) => this.setState({ person: results }));
  }

  render() {
    const persons = this.state.person.map((item, i) => (
     
        { item.name.first }
        <span>{ item.cell }, { item.email }</span>
     
    ));

    return (
     
        { persons }
     
    );
  }
}


您可能要查看Flux体系结构。我还建议检查React-Redux的实现。将您的api调用放入您的操作中。这比将它们全部放入组件中要干净得多。

操作是一种帮助程序方法,您可以调用这些方法来更改应用程序状态或进行api调用。


componentDidMount中使用fetch方法更新状态:

1
2
3
4
componentDidMount(){
  fetch('https://randomuser.me/api/')
      .then(({ results }) => this.setState({ person: results }));
}

讨论已经有一段时间了,@ Alexander T.的回答为像我这样的更新的React提供了很好的指南。我将分享一些其他知识,这些知识涉及多次调用同一API来刷新组件,我认为这可能是新手开始时可能会遇到的常见问题。

componentWillReceiveProps(nextProps),来自官方文档:

If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.

我们可以得出结论,这里是我们处理来自父组件的道具,进行API调用和更新状态的地方。

基于@Alexander T.的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default class UserList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {person: []};
  }

  componentDidMount() {
   //For our first load.
   this.UserList(this.props.group); //maybe something like"groupOne"
  }

  componentWillReceiveProps(nextProps) {
    // Assuming parameter comes from url.
    // let group = window.location.toString().split("/")[*indexParameterLocated*];
    // this.UserList(group);

    // Assuming parameter comes from props that from parent component.
    let group = nextProps.group; // Maybe something like"groupTwo"
    this.UserList(group);
  }

  UserList(group) {
    $.getJSON('https://randomuser.me/api/' + group)
      .then(({ results }) => this.setState({ person: results }));
  }

  render() {
    return (...)
  }
}

更新资料

componentWillReceiveProps()将被弃用。

我认为生命周期中只有一些方法(所有这些方法都在Doc中),在一般情况下,我认为它们与部署API有关:
enter image description here

通过参考上图:

  • componentDidMount()中部署API

    在此处进行API调用的正确方案是,该组件的内容(来自API的响应)将是静态的,componentDidMount()仅在组件安装时触发一次,甚至新的props从父组件传递或具有操作铅re-rendering
    组件会检查差异以重新渲染,但不会重新安装。
    来自doc的引用:

If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.

  • static getDerivedStateFromProps(nextProps, prevState)中部署API

我们应该注意到,有两种类型的组件更新,当前组件中的setState()不会导致此方法触发,但是从父组件进行重新渲染或新的道具会触发。
我们发现该方法在安装时也会触发。

如果我们想使用当前组件(例如模板),并且这是API的新参数是来自父组件的道具,那么这是部署API的合适位置。
我们收到来自API的不同响应,并在此处返回新的state来更改此组件的内容。

例如:
我们在父组件中有一个用于不同汽车的下拉列表,该组件需要显示所选汽车的详细信息。

  • componentDidUpdate(prevProps, prevState)中部署API

static getDerivedStateFromProps()不同,此方法在每个渲染(初始渲染除外)之后立即调用。我们可以调用API并在一个组件中呈现差异。

扩展前面的示例:
显示汽车详细信息的组件可能包含该汽车系列的列表,如果我们要查看2013年生产的汽车,我们可以单击或选择或...该列表项以引导第一个setState()来反映此行为( (例如突出显示列表项),并在以下componentDidUpdate()中发送带有新参数(状态)的请求。收到响应后,我们再次setState()呈现汽车详细信息的不同内容。为了防止后面的componentDidUpdate()导致无限循环,我们需要在此方法的开头利用prevState来比较状态,以确定是否发送API并呈现新内容。

确实可以像使用道具的static getDerivedStateFromProps()一样使用此方法,但是需要利用prevProps处理props的更改。并且我们需要与componentDidMount()合作来处理初始API调用。

来自doc的引用:

... This is also a good place to do network requests as long as you
compare the current props to previous props ...


我希望您来看看redux
http://redux.js.org/index.html

它们具有处理异步调用(即API调用)的非常明确的方式,并且我建议使用fetch或请求npm包,而不是使用jQuery进行API调用,现代浏览器目前支持fetch,但也可以使用shim服务器端。

还有另一个令人惊奇的软件包超级代理,它在发出API请求时有很多选择,而且非常易于使用。


1)您可以使用Fetch API从End Point获取数据:

为用户获取所有Github休止符的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  /* Fetch GitHub Repos */
  fetchData = () => {

       //show progress bar
      this.setState({ isLoading: true });

      //fetch repos
      fetch(`https://api.github.com/users/hiteshsahu/repos`)
      .then(response => response.json())
      .then(data => {
        if (Array.isArray(data)) {
          console.log(JSON.stringify(data));
          this.setState({ repos: data ,
                         isLoading: false});
        } else {
          this.setState({ repos: [],
                          isLoading: false  
                        });
        }
      });
  };

2)其他替代品是Axios

Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  import axios from"axios";

 /* Fetch GitHub Repos */
  fetchDataWithAxios = () => {

     //show progress bar
      this.setState({ isLoading: true });

      // fetch repos with axios
      axios
          .get(`https://api.github.com/users/hiteshsahu/repos`)
          .then(result => {
            console.log(result);
            this.setState({
              repos: result.data,
              isLoading: false
            });
          })
          .catch(error =>
            this.setState({
              error,
              isLoading: false
            })
          );
}

现在,您可以选择使用componentDidMount中的任何一种策略来获取数据

1
2
3
4
5
6
7
8
9
class App extends React.Component {
  state = {
    repos: [],
   isLoading: false
  };

  componentDidMount() {
    this.fetchData ();
  }

同时,您可以在加载数据时显示进度条

1
   {this.state.isLoading && <LinearProgress />}

React v16文档中的这一部分将回答您的问题,请继续阅读有关componentDidMount()的内容:

componentDidMount()

componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().

如您所见,componentDidMount被认为是进行api调用,访问节点的最佳位置和周期,这意味着此时可以安全地进行调用,更新视图或在文档准备就绪时执行任何操作(如果可以)使用jQuery,它应该以某种方式提醒您document.ready()函数,在这里您可以确保一切准备就绪,可以在代码中进行任何操作...


渲染函数应该是纯函数,这意味着它仅使用状态和道具进行渲染,而从不尝试在渲染中修改状态,这通常会导致难看的错误并显着降低性能。如果您分开数据获取并在React App中提出问题,这也是一个好点。我建议您阅读这篇文章,它很好地解释了这个想法。 https://medium.com/@learnreact/container-components-c0e67432e005#.sfydn87nm


您还可以在函数组件中使用钩子获取数据

带有api调用的完整示例:https://codesandbox.io/s/jvvkoo8pq3

第二个示例:https://jsfiddle.net/bradcypert/jhrt40yv/6/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const Repos = ({user}) => {
  const [repos, setRepos] = React.useState([]);

  React.useEffect(() => {
    const fetchData = async () => {
        const response = await axios.get(`https://api.github.com/users/${user}/repos`);
        setRepos(response.data);
    }

    fetchData();
  }, []);

  return (
 
    {repos.map(repo =>
      {repo.name}
    )}
 
  );
}

ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))

作为外部API调用的最佳实践,是React Lifecycle方法componentDidMount(),在执行API调用之后,您应该更新本地状态以触发新的render()方法调用,然后更新后的本地状态中的更改将应用于组件视图。

作为React中初始外部数据源调用的另一种选择,指向该类的Constructor()方法。构造函数是在初始化组件对象实例时执行的第一个方法。您可以在高阶组件的文档示例中看到此方法。

不应将方法componentWillMount()和UNSAFE_componentWillMount()用于外部API调用,因为它们已被弃用。在这里,您会看到常见原因,为什么不赞成使用此方法。

无论如何,您绝不能使用render()方法或直接从render()调用的方法作为外部API调用的点。如果这样做,您的应用程序将被阻止。


最好将axios用于支持取消,拦截器等功能的api请求。与axios一起,我将react-redux用于状态管理,并将redux-saga / redux-thunk用于副作用。


一种干净的方法是使用try / catch函数在componentDidMount内部进行异步API调用。

调用API时,我们会收到响应。然后,我们对其应用JSON方法,以将响应转换为JavaScript对象。然后,从该响应对象中仅获取他的子对象" results"(data.results)。

首先,我们将状态为" userList"的数组定义为一个空数组。一旦进行API调用并从该API接收数据,我们就会使用setState方法将"结果"分配给userList。

在render函数内部,我们告诉userList将来自状态。由于userList是对象数组,我们通过它映射,以显示每个对象"用户"的图片,名称和电话号码。要检索此信息,我们使用点表示法(例如user.phone)。

注意:根据您的API,您的响应可能看起来有所不同。 Console.log整个"响应",以查看您需要从中获取哪些变量,然后在setState中分配它们。

UserList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { Component } from"react";

export default class UserList extends Component {
   state = {
      userList: [], // list is empty in the beginning
      error: false
   };

   componentDidMount() {
       this.getUserList(); // function call
   }

   getUserList = async () => {
       try { //try to get data
           const response = await fetch("https://randomuser.me/api/");
           if (response.ok) { // ckeck if status code is 200
               const data = await response.json();
               this.setState({ userList: data.results});
           } else { this.setState({ error: true }) }
       } catch (e) { //code will jump here if there is a network problem
   this.setState({ error: true });
  }
};

  render() {
  const { userList, error } = this.state
      return (
         
            {userList.length > 0 && userList.map(user => (
             
                  <img src={user.picture.medium} alt="user"/>
                 
                      {user.name.first}{user.name.last}
                      {user.phone}
                      {user.email}
                 
             
            ))}
            {error && Sorry, can not display the data}
         
      )
}}