react hooks使用_如何使用React Hooks构建电影搜索应用程序

react hooks使用

React hooks is finally here!!! And I know there has been a whole lot of excitement around the possibilities that this new set of APIs present. If you’re still skeptical about it, then I urge you to check out this medium article so as to understand the problems that the team was trying to solve when they proposed Hooks.

React钩终于在这里 ! 我知道这套新的API所带来的可能性让人非常兴奋。 如果您仍然对此持怀疑态度,那么我敦促您阅读这篇中级文章 ,以了解团队在提出Hooks时试图解决的问题。

I initially wasn’t as excited as the rest of the community, so I decided to wait until it was officially released in case there were any API changes. So, over the weekend after it was released I decided to read about it and surprisingly (not) there was a lot of articles and posts about hooks and how to get started with them.

最初,我没有其他社区那么兴奋,所以我决定等到API正式发布后再发布。 因此,在它发布之后的一个周末,我决定阅读它,令人惊讶的是(没有),有很多关于钩子以及如何开始使用它们的文章和帖子。

I know some might say “Another hooks article, really?”, and to them, I say “Yes…yes and there’s more where that came from”. For this article, we will be building a very simple app using Hooks. In essence, we are not going to be using any class components in this application. And I will be explaining how a few of the APIs work and how they should be used in any application that we might be building.

我知道有些人可能会说“是真的吗?”,对他们来说,我说:“是的,而且还有更多的来历。” 对于本文,我们将使用Hooks构建一个非常简单的应用程序。 本质上,我们不会在此应用程序中使用任何类组件。 我将解释一些API的工作原理,以及在我们可能正在构建的任何应用程序中应如何使用它们。

Below is an image of what the app will look like once we are done:

以下是完成后应用程序的外观图像:

Basically, the app will be able to search for movies via the OMDB API and render the results to the user. The reason for building the app is just so that we can get a better understanding of using hooks in an application, which helps in understanding the role some of the hooks we will use can play in your own real-world apps. Some things are needed before we get into building the app:

基本上,该应用程序将能够通过OMDB API搜索电影并将结果呈现给用户。 构建应用程序的原因仅仅是为了使我们能够更好地了解在应用程序中使用挂钩,这有助于理解我们将使用的某些挂钩在您自己的真实应用程序中所扮演的角色。 在构建应用程序之前,需要做一些事情:

  • Node (≥ 6)

    节点(≥6)
  • A cool text Editor

    很棒的文字编辑器
  • An API key from OMDB (You can obtain it here or use mine)

    OMDB的API密钥(您可以在此处获得或使用我的)

Great, once we have that then the next step is to set up the React app. For this tutorial we will be using create-react-app — it’s a really awesome tool for setting up a React app without having to deal with all the configurations that come with starting from scratch. You can create a new app by typing:

太好了,一旦有了,那么下一步就是设置React应用。 在本教程中,我们将使用create-react-app-这是一个非常出色的工具,用于设置React应用,而无需处理从头开始的所有配置。 您可以通过键入以下内容来创建新应用:

If you prefer to copy and paste then:

如果您喜欢复制和粘贴,请执行以下操作:

create-react-app hooked # "hooked" is the name off our app

# if you haven't installed create-react-app then type the following

npm install -g create-react-app

Once that’s done we should have a folder called “Hooked” with a directory structure as shown below:

完成后,我们应该有一个名为“ Hooked”的文件夹,其目录结构如下所示:

We will have 4 components in this application, so let’s outline each one and its functionality:

在此应用程序中,我们将有4个组件,因此让我们概述每个组件及其功能:

  • App.js — It will be the parent component for the other 3. It will also contain the function that handles the API request and it will have a function that calls the API during the component’s initial render.

    App.js —它将是其他3的父组件。它还将包含处理API请求的函数,并且将具有在组件的初始呈现期间调用API的函数。
  • Header.js — A simple component that renders the app header and accepts a title prop

    Header.js —一个简单的组件,可呈现应用程序标题并接受标题道具
  • Movie.js — It renders each movie. The movie object is simply passed into it as props.

    Movie.js —渲染每部电影。 电影对象只是作为道具传递给它的。
  • Search.js — Contains a form with the input element and the search button, contains functions that handle the input element and resets the field, and also contains a function that calls the search function which is passed as props to it.

    Search.js —包含带有输入元素和搜索按钮的表单,包含处理输入元素并重置字段的函数,还包含调用作为道具传递给它的搜索函数的函数。

Let’s start creating, in the src directory, a new folder and name it components because that’s where all our components will be. We will then move the App.js file into that folder. Then, we will create the Header component. Create a file called Header.js and add the following code to it:

让我们开始在src目录中创建一个新文件夹并将其命名为components因为这是我们所有组件所在的位置。 然后,我们将App.js文件移动到该文件夹​​中。 然后,我们将创建Header组件。 创建一个名为Header.js的文件,并向其中添加以下代码:

import React from "react";

const Header = (props) => {
  return (
    <header className="App-header">
      <h2>{props.text}</h2>
    </header>
  );
};

export default Header;

This component doesn’t require that much of an explanation — it’s basically a functional component that renders the header tag with the text props.

这个组件不需要太多解释-它基本上是一个功能组件,使用text props渲染header标记。

Let's not forget to update the import in our index.js file:

让我们不要忘记更新index.js文件中的导入:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App'; // this changed
import * as serviceWorker from './serviceWorker';


ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA


serviceWorker.unregister();

And also update our App.css with these styles (not compulsory):

并使用以下样式(非强制性)更新App.css

.App {
  text-align: center;
}

.App-header {
  background-color: #282c34;
  height: 70px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
  padding: 20px;
  cursor: pointer;
}

.spinner {
  height: 80px;
  margin: auto;
}

.App-intro {
  font-size: large;
}

/* new css for movie component */

* {
  box-sizing: border-box;
}

.movies {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
}

.App-header h2 {
  margin: 0;
}

.add-movies {
  text-align: center;
}

.add-movies button {
  font-size: 16px;
  padding: 8px;
  margin: 0 10px 30px 10px;
}

.movie {
  padding: 5px 25px 10px 25px;
  max-width: 25%;
}

.errorMessage {
  margin: auto;
  font-weight: bold;
  color: rgb(161, 15, 15);
}


.search {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 10px;
}


input[type="submit"] {
  padding: 5px;
  background-color: transparent;
  color: black;
  border: 1px solid black;
  width: 80px;
  margin-left: 5px;
  cursor: pointer;
}


input[type="submit"]:hover {
  background-color: #282c34;
  color: antiquewhite;
}


.search > input[type="text"]{
  width: 40%;
  min-width: 170px;
}

@media screen and (min-width: 694px) and (max-width: 915px) {
  .movie {
    max-width: 33%;
  }
}

@media screen and (min-width: 652px) and (max-width: 693px) {
  .movie {
    max-width: 50%;
  }
}


@media screen and (max-width: 651px) {
  .movie {
    max-width: 100%;
    margin: auto;
  }
}

Once we have that, the next thing is to create the Movie component. We will do that by creating a file called Movie.js and adding the following code:

一旦有了这些,下一步就是创建Movie组件。 我们将通过创建一个名为Movie.js的文件并添加以下代码来做到这一点:

import React from "react";

const DEFAULT_PLACEHOLDER_IMAGE =
  "https://m.media-amazon.com/images/M/MV5BMTczNTI2ODUwOF5BMl5BanBnXkFtZTcwMTU0NTIzMw@@._V1_SX300.jpg";


const Movie = ({ movie }) => {
  const poster =
    movie.Poster === "N/A" ? DEFAULT_PLACEHOLDER_IMAGE : movie.Poster;
  return (
    <div className="movie">
      <h2>{movie.Title}</h2>
      <div>
        <img
          width="200"
          alt={`The movie titled: ${movie.Title}`}
          src={poster}
        />
      </div>
      <p>({movie.Year})</p>
    </div>
  );
};


export default Movie;

This requires more of an explanation, but it’s also just a presentational component (it doesn’t have any internal state) that renders the movie title, image, and year. The reason for the DEFAULT_PLACEHOLDER_IMAGE is because some movies retrieved from the API do not have images, so we will render a placeholder image instead of a broken link.

这需要更多的解释,但是它只是呈现电影标题,图像和年份的表示性组件(没有任何内部状态)。 之所以使用DEFAULT_PLACEHOLDER_IMAGE是因为从API检索的某些电影没有图像,因此我们将呈现一个占位符图像,而不是断开的链接。

Now we will create the Search component. This part is exciting because in the past, in order to handle internal state, we would have to create a class component…but not anymore! Because with hooks we can have a functional component handle its own internal state. Let’s create a file named Search.js and in that file, we will add the following code:

现在,我们将创建Search组件。 这部分令人兴奋,因为在过去,为了处理内部状态,我们将不得不创建一个类组件……但现在不再了! 因为使用钩子,我们可以使功能组件处理其自身的内部状态。 让我们创建一个名为Search.js文件,然后在该文件中添加以下代码:

import React, { useState } from "react";


const Search = (props) => {
  const [searchValue, setSearchValue] = useState("");
  
  const handleSearchInputChanges = (e) => {
    setSearchValue(e.target.value);
  }

  const resetInputField = () => {
    setSearchValue("")
  }

  const callSearchFunction = (e) => {
    e.preventDefault();
    props.search(searchValue);
    resetInputField();
  }

  return (
      <form className="search">
        <input
          value={searchValue}
          onChange={handleSearchInputChanges}
          type="text"
        />
        <input onClick={callSearchFunction} type="submit" value="SEARCH" />
      </form>
    );
}

export default Search;

This is so exciting!!! I’m sure you’ve just seen the first hooks API that we are going to use, and it’s called useState . As the name implies, it lets us add React state to function components. The useState hook accepts one argument which is the initial state, and then it returns an array containing the current state (equivalent to this.state for class components) and a function to update it (equivalent to this.setState ).

这太令人兴奋了!!! 我确定您已经看到了我们将要使用的第一个钩子API,它称为useState 。 顾名思义,它使我们可以将React状态添加到功能组件中。 useState挂钩接受一个参数,该参数是初始状态,然后它返回一个包含当前状态(等效于类组件的this.state )和更新它的函数(等效于this.setState )的this.setState

In our case, we are passing our current state as the value for the search input field. When the onChange event is called, the handleSearchInputChanges function is called which calls the state update function with the new value. The resetInputField function basically called the state update function (setSearchValue) with an empty string in order to clear the input field. Check this out to know more about the useState API.

在本例中,我们将当前状态作为搜索输入字段的值。 调用onChange事件时,将调用handleSearchInputChanges函数,该函数使用新值调用状态更新函数。 resetInputField函数基本上称为状态更新函数( setSearchValue ),带有一个空字符串,以清除输入字段。 检查出更多的了解useState API。

Finally, we will update the App.js file with the following code:

最后,我们将使用以下代码更新App.js文件:

import React, { useState, useEffect } from "react";
import "../App.css";
import Header from "./Header";
import Movie from "./Movie";
import Search from "./Search";


const MOVIE_API_URL = "https://www.omdbapi.com/?s=man&apikey=4a3b711b"; // you should replace this with yours


const App = () => {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const [errorMessage, setErrorMessage] = useState(null);

    useEffect(() => {
    fetch(MOVIE_API_URL)
      .then(response => response.json())
      .then(jsonResponse => {
        setMovies(jsonResponse.Search);
        setLoading(false);
      });
  }, []);

    const search = searchValue => {
    setLoading(true);
    setErrorMessage(null);

    fetch(`https://www.omdbapi.com/?s=${searchValue}&apikey=4a3b711b`)
      .then(response => response.json())
      .then(jsonResponse => {
        if (jsonResponse.Response === "True") {
          setMovies(jsonResponse.Search);
          setLoading(false);
        } else {
          setErrorMessage(jsonResponse.Error);
          setLoading(false);
        }
      });
  	};

    
    return (
     <div className="App">
      <Header text="HOOKED" />
      <Search search={search} />
      <p className="App-intro">Sharing a few of our favourite movies</p>
      <div className="movies">
        {loading && !errorMessage ? (
         <span>loading...</span>
         ) : errorMessage ? (
          <div className="errorMessage">{errorMessage}</div>
        ) : (
          movies.map((movie, index) => (
            <Movie key={`${index}-${movie.Title}`} movie={movie} />
          ))
        )}
      </div>
    </div>
  );
};


export default App;

Let’s go over the code: we are using 3 useState functions so yes, we can have multiple useState functions in one component. The first is used to handle the loading state (it renders a ‘loading…’ text when loading is set to true). The second is used to handle the movies array that is gotten from the server. And finally the third is used to handle any errors that might occur when making the API request.

我们来看一下代码:我们正在使用3个useState函数,是的,我们可以在一个组件中拥有多个useState函数。 第一个用于处理加载状态(将loading设置为true时,它将呈现“ loading…”文本)。 第二个用于处理从服务器获取的电影数组。 最后,第三个用于处理发出API请求时可能发生的任何错误。

And after that, we come across the second hooks API that we are using in the app: the useEffect hook. This hook basically lets you perform side effects in your function components. By side effects we mean things like data fetching, subscriptions, and manual DOM manipulations. The best part about this hook is this quote from the React official docs:

然后,我们遇到了应用程序中使用的第二个挂钩API: useEffect挂钩。 该钩子基本上使您可以在功能组件中执行副作用。 所谓副作用,是指诸如数据获取,订阅和手动DOM操作之类的事情。 关于这个钩子最好的部分是React官方文档中的这句话:

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

如果您熟悉React类的生命周期方法,则可以将useEffect Hook视为componentDidMountcomponentDidUpdatecomponentWillUnmount结合使用。

This is because useEffect gets called after the first render (componentDidMount) and also after every update ( componentDidUpdate ).

这是因为useEffect在第一个渲染( componentDidMount )之后以及每次更新( componentDidUpdate )之后都被调用。

I know you might be wondering how this is similar to componentDidMount if it gets called after every update. Well, it’s because of the useEffect function accepts two arguments, the function that you want to run and a second argument which is an array. In that array we just pass in a value that tells React to skip applying an effect if the value passed in hasn’t changed.

我知道您可能想知道如果每次更新后都调用它与componentDidMount有何相似之处。 好吧,这是因为useEffect函数接受两个参数,一个是您要运行的函数,另一个是数组。 在该数组中,我们只是传入一个值,该值告诉React如果传入的值未更改,则跳过应用效果。

According to the docs, it’s similar to when we add a conditional statement in our componentDidUpdate :

根据文档,这类似于我们在componentDidUpdate添加条件语句时的情况:

// for class components
componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}


// using hooks it will become
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

In our case, we don’t have any value that changes, so we can pass in an empty array which tells React this effect should be called once.

在我们的例子中,我们没有任何改变的值,因此我们可以传入一个空数组,该数组告诉React这个效果应该被调用一次。

As you can see, we have 3 useState functions that are somewhat related, and it should be possible for them to be combined in a way. Thankfully, the React team has us covered because they made a hook that helps with this — and that hook is called useReducer . Let’s convert our App component to use our new hook, so our App.js will now look like this:

如您所见,我们有3个useState函数有些相关,并且应该以某种方式组合它们。 值得庆幸的是,React团队为我们提供了帮助,因为他们制作了一个有助于此操作的钩子-该钩子称为useReducer 。 让我们将App组件转换为使用新的钩子,这样我们的App.js现在将如下所示:

import React, { useReducer, useEffect } from "react";
import "../App.css";
import Header from "./Header";
import Movie from "./Movie";
import Search from "./Search";


const MOVIE_API_URL = "https://www.omdbapi.com/?s=man&apikey=4a3b711b";


const initialState = {
  loading: true,
  movies: [],
  errorMessage: null
};


const reducer = (state, action) => {
  switch (action.type) {
    case "SEARCH_MOVIES_REQUEST":
      return {
        ...state,
        loading: true,
        errorMessage: null
      };
    case "SEARCH_MOVIES_SUCCESS":
      return {
        ...state,
        loading: false,
        movies: action.payload
      };
    case "SEARCH_MOVIES_FAILURE":
      return {
        ...state,
        loading: false,
        errorMessage: action.error
      };
    default:
      return state;
  }
};



const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

    useEffect(() => {
    
        fetch(MOVIE_API_URL)
            .then(response => response.json())
            .then(jsonResponse => {
        
            dispatch({
                type: "SEARCH_MOVIES_SUCCESS",
                payload: jsonResponse.Search
        	});
      	});
  	}, []);

    const search = searchValue => {
    	dispatch({
      	type: "SEARCH_MOVIES_REQUEST"
    	});
	
        fetch(`https://www.omdbapi.com/?s=${searchValue}&apikey=4a3b711b`)
      	.then(response => response.json())
      	.then(jsonResponse => {
        	if (jsonResponse.Response === "True") {
          	dispatch({
                type: "SEARCH_MOVIES_SUCCESS",
                payload: jsonResponse.Search
          	});
        	} else {
          	dispatch({
                type: "SEARCH_MOVIES_FAILURE",
                error: jsonResponse.Error
          	});
          }
      	});
	  };

    const { movies, errorMessage, loading } = state;

    return (
    <div className="App">
      <Header text="HOOKED" />
      <Search search={search} />
      <p className="App-intro">Sharing a few of our favourite movies</p>
      <div className="movies">
        {loading && !errorMessage ? (
          <span>loading... </span>
        ) : errorMessage ? (
          <div className="errorMessage">{errorMessage}</div>
        ) : (
          movies.map((movie, index) => (
            <Movie key={`${index}-${movie.Title}`} movie={movie} />
          ))
        )}
      </div>
    </div>
  );
};

export default App;

So, if all went well then we should see no change in the behavior of the app. Now let's go over how the useReducer hook works.

因此,如果一切顺利,那么我们应该不会看到应用程序行为的任何变化。 现在让我们看一下useReducer挂钩的工作方式。

The hook takes 3 arguments, but for our use case we will be using only 2. A typical useReducer hook will look like this:

该挂钩接受3个参数,但在我们的用例中,我们将仅使用2个。典型的useReducer挂钩如下所示:

const [state, dispatch] = useReducer(
    reducer,
    initialState
);

The reducer argument is similar to what we use in Redux, which looks like this:

reducer参数类似于我们在Redux中使用的参数,看起来像这样:

const reducer = (state, action) => {
  switch (action.type) {
    case "SEARCH_MOVIES_REQUEST":
      return {
        ...state,
        loading: true,
        errorMessage: null
      };
    case "SEARCH_MOVIES_SUCCESS":
      return {
        ...state,
        loading: false,
        movies: action.payload
      };
    case "SEARCH_MOVIES_FAILURE":
      return {
        ...state,
        loading: false,
        errorMessage: action.error
      };
    default:
      return state;
  }
};

The reducer takes in the initialState and the action, so based on the action type, the reducer returns a new state object. For example, if the type of action that is dispatched is SEARCH_MOVIES_REQUEST , the state is updated with the new object where the value for loading is true and errorMessage is null.

精简器接受initialState和操作,因此精简器根据操作类型返回一个新的状态对象。 例如,如果调度的操作类型为SEARCH_MOVIES_REQUEST ,则状态将使用新对象更新,其中要loading值为true,而errorMessage为null。

Another thing to note is that in our useEffect , we are now dispatching an action with the payload as the movies array we are getting from the server. Also, in our search function, we are dispatching three different actions actually.

还要注意的另一件事是,在useEffect ,我们现在使用从服务器获取的movies数组调度带有有效负载的动作。 另外,在search功能中,我们实际上是在分派三个不同的动作。

  • One action is the SEARCH_MOVIES_REQUEST action which updates our state object, making loading=true and errorMessage = null.

    一种动作是SEARCH_MOVIES_REQUEST动作,它更新我们的状态对象,使loading=true and errorMessage = null

  • If the request is successful then we dispatch another action with the type SEARCH_MOVIES_SUCCESS that updates our state object making loading=false and movies = action.payload where the payload is the movies array gotten from OMDB.

    如果请求成功,那么我们将分派另一个类型为SEARCH_MOVIES_SUCCESS动作,该SEARCH_MOVIES_SUCCESS将更新我们的状态对象,使loading=false and movies = action.payload SEARCH_MOVIES_SUCCESS loading=false and movies = action.payload ,其中有效负载是从OMDB获得的movie数组。

  • If there is an error, we will instead dispatch a different action with the type SEARCH_MOVIES_FAILURE that updates our state object making loading=false and errorMessage = action.error where the action.error is the error message gotten from the server.

    如果发生错误,我们将分派类型为SEARCH_MOVIES_FAILURE的其他操作,该操作更新状态对象,使loading=false and errorMessage = action.error ,其中action.error是从服务器action.error的错误消息。

To know more about the useReducer hook you can check out the official documentation.

要了解有关useReducer挂钩的更多信息,可以查看官方文档

结语 (Wrapping up)

Wow!!! We’ve come a long way and I’m sure you are as excited as I am about the possibilities of hooks. For me personally, it is so much easier to introduce beginners to React, because I don’t need to explain how classes work or how this works, or how bind works in JS, which is awesome IMO.

哇!!! 我们已经走了很长一段路,我相信您对钩子的可能性同样感到兴奋。 对于我个人来说,它是如此容易得多引进初学者React,因为我不需要解释如何类工作或如何this工作,或如何bind工作在JS,这是真棒IMO。

We’ve only touched a few hooks in this tutorial, and we didn’t even cover features like creating our own custom hooks. If you have some other use cases for hooks or you’ve implemented your own custom hook, please do drop a comment and join in on the excitement.

在本教程中,我们仅涉及了几个钩子,甚至没有介绍创建自己的自定义钩子之类的功能 。 如果您还有其他一些用例,或者已经实现了自己的自定义钩子,请添加注释并加入其中。

NOTE: This article is not related to the previous one about Webpack, a subsequent article for that is already under construction 😬.

注意:本文与有关Webpack的上一篇文章无关 ,有关后者的后续文章已经在构建中。

This is the link to the GitHub repo for this article.

这是本文的GitHub存储库的链接

翻译自: https://www.freecodecamp.org/news/how-to-build-a-movie-search-app-using-react-hooks-24eb72ddfaf7/

react hooks使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值