JavaScript >> Javascript 文檔 >  >> React

利用 React Hooks,一個實際的例子

  1. 簡介
  2. 項目設置
  3. 依賴項
  4. 使用狀態
  5. 使用效果
  6. 項目鏈接
  7. 資源

簡介

先決條件:


This article is for people who are familiar with the basic concepts of react.

Hooks 是 react 庫中的一個強大功能,它結合了諸如 props、state、context、refs 和生命週期之類的 react 概念。 React 16.8.0 及更高版本支持此功能。鉤子是為:

  1. 簡單
  2. 性能

在鉤子出現之前,只能在 React 類組件中聲明狀態。除了每當 stateful components 在 react 中提到了唯一想到的是 class componentfunctional components 被視為stateless components 但現在情況已不再如此。感謝 react hooks functional components 現在可以聲明狀態和您能想到的任何其他反應概念。因此,react hooks 可以這樣描述:


Hooks are functions that let you “hook into” React state and lifecycle features from functional components.

這為這些術語帶來了新的區別:

  • Stateful Components :這些是聲明和管理狀態的類組件或功能組件。它們通常是 parent-components
  • Stateless Components :這些是不聲明或管理狀態的類組件或功能組件。它們通常是 child-components

儘管有關 hooks 的 react 文檔非常詳細,但我堅信掌握新概念的最佳方法是實踐,這就是為什麼我編寫了我們將在本文中進行的迷你項目。

項目設置

為了向您展示如何利用反應鉤子,我們將構建一個 Instagram clone 一起。下面是該項目的現場演示


I hope you're as excited as I am

我們將使用 create-react-app 對於這個項目。所以對於初學者來說,打開你的命令行並輸入以下內容:

npx create-react-app instagram-app

現在 cd 進入我們創建的 instagram-app 文件夾並安裝以下依賴項

cd instagram-app
npm install faker moment styled-components uuid

依賴關係

  • faker 是一個生成隨機數據輸入的 npm 包
  • moment 是一個用於日期格式化的 npm 包
  • styled-components 是一個 npm 包,我們將使用它來設置組件的樣式。它利用標記的模板文字來為您的組件設置樣式,並且無需在我們的項目中創建 CSS 文件。
  • uuid 這是隨機 uuid 生成器

現在我們要創建我們的組件文件夾

在您的命令行中鍵入以下內容

cd src
mkdir -p component/Form component/Login component/PostContainer component/SearchBar component/CommentSection component/Authentication

這會在我們的項目中創建以下文件夾

充實我們的組件

在您的 src 文件夾中並鍵入以下內容

touch component/PostContainer/PostContainer.js component/Form/Form.js component/Login/Login.js component/SearchBar/SearchBar.js component/CommentSection/CommentSection.js

這將在每個組件目錄中分別創建一個js文件。

由於本文關注的是 React Hooks 及其實現,因此我將介紹使用 Hooks 的代碼片段。有哪些

  • App.js
  • PostContainer.js
  • 登錄.js

完整的項目 repo 和託管應用程序的鏈接可以在下面找到:

Instagram-克隆

Instagram-克隆-netlify

我們將在這個項目中使用的反應鉤子是 useStateuseEffect 掛鉤。

使用狀態

這在 functional component 中調用 為它添加一些本地狀態。這允許我們在應用程序中重用和共享有狀態邏輯。

使用效果

這使功能組件能夠以與 componentDidMount 大致相同的方式執行副作用 , componentDidUpdatecomponentWillUnmount 方法作用於類組件。

要在我們的反應中使用狀態,我們必須這樣導入它們:

import React, { useState, useEffect } from "react";

在我們的 App.js 文件中進行以下更改

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import uuidv4 from "uuid/v4";
import data from "./dummy-data";
import SearchBar from "./component/SearchBar/SearchBar";
import PostContainer from './component/PostContainer/PostContainer';

const preprocessData = data.map(post => {
  return {
    ...post,
    postId: uuidv4(),
    show: "on"
  };
});

function App() {
  const [posts, setPost] = useState([]);
  const [search, setSearch] = useState("");

  useEffect(() => {
    const allData = localStorage.getItem("posts");
    let postData;
    if (allData) {
      postData = JSON.parse(allData);
    } else {
      localStorage.setItem("posts", JSON.stringify(preprocessData));
      postData = JSON.parse(localStorage.getItem("posts"));
    }
    setPost(postData);
  }, []);

const handleSearch = e => {
    e.preventDefault();
    const data = posts;
    setSearch(e.target.value.trim());
      const query = data.map(post => {
        if (!post.username.trim().toLowerCase().includes(e.target.value.trim())) {
          return {
            ...post,
            show: "off"
          };
        }
        return {
          ...post,
          show: "on"
        };
      });
      setPost(query);
  };

  return (
    <AppContainer>
      <SearchBar />
      <PostContainer />
    </AppContainer>
  );
}

export default App;


解釋

  1. 在我們的 App.js 文件中,我們導入了原始 data 並使用以下代碼行對其進行了一些調整
const preprocessData = data.map(post => {
  return {
    ...post,
    postId: uuidv4(),
    show: "on"
  };
});

所有這些都是為我們的虛擬數據中的每個帖子提供一個 postId 和一個 show 財產。我們還導入了我們需要的反應鉤子

import React, { useState, useEffect } from "react";
  1. 在我們的 App 組件中,我們初始化了我們的狀態。 Note 語法。
  const [posts, setPost] = useState([]);
  const [search, setSearch] = useState("");
  • useState 返回一對錶示 current-state 的值 (帖子)和 update-function 更新狀態(setPost 和 setSearch)。 setPostsetSearch 分別類似於 this.setState class components的方法 .


"The key difference between the
this.setStatemethod of class components and the update-function of the useState react hook is that it does not merge the old state with the new state"

useState() 方法接受一個參數,即 initial state (即useState([]),useState(“”))並且僅在第一次渲染中使用。參數可以是 null、字符串、數字或對象。

  1. 接下來,我們處理一些副作用。很像 componentDidMount 類組件,我們將使用 useEffectlocalStorage 掛載和渲染數據的函數 陳述
useEffect(() => {
    const allData = localStorage.getItem("posts");
    let postData;
    if (allData) {
      postData = JSON.parse(allData);
    } else {
      localStorage.setItem("posts", JSON.stringify(preprocessData));
      postData = JSON.parse(localStorage.getItem("posts"));
    }
    setPost(postData);
  }, []);
  • useEffect 有兩個參數。 callback function 它處理副作用和效果必須響應的狀態數組。這很像為一個狀態添加一個事件監聽器。在上面的效果中,我們輸入了一個空數組作為第二個參數,因為我們只想在應用程序啟動時調用這個效果一次(就像 componentDidMount 一樣)。如果未指定數組,則組件將在每次狀態更改時重新呈現。

現在我們需要將此狀態作為道具傳遞給我們的子組件。
對我們的 App.js 文件的 JSX 進行以下更新

return (
    <AppContainer>
      <SearchBar search={search} handleSearch={handleSearch} />
      {posts.map((userPost, index) => {
        return <PostContainer 
        key={index} 
        props={userPost} 

        />;
      })}
    </AppContainer>
  );

現在 PosContainer.js 和 SearchBar.js 需要將它們收到的狀態渲染為 props。

在我們的 PostContainer.js 文件中,我們將利用 react hooks 的能力來重用有狀態的邏輯而不改變我們的組件層次結構。

PostContainer.js


const PostContainer = ({ props }) => {
    const {
      postId,
      comments,
      thumbnailUrl,
      imageUrl,
      timestamp,
      likes,
      username,
      show
    } = props;
    const commentDate = timestamp.replace(/th/, "");
    const [inputValue, setInputValue] = useState("");
    const [inputComment, setInputComment] = useState(comments);
    const [createdAt, setCreatedAt] = useState(
      moment(new Date(commentDate), "MMM D LTS").fromNow()
    );

    const [addLikes, updateLikes] = useState(likes);

    useEffect(()=>{
      const post = JSON.parse(localStorage.getItem("posts"));
      const postUpdate = post.map((userPost) => {
        if(postId === userPost.postId) {
          return {
            ...userPost, comments: inputComment, timestamp: `${moment(new Date(), "MMM D LTS")}`, likes: addLikes
          }
        }
        return userPost;
      });
      localStorage.setItem("posts", JSON.stringify(postUpdate));
    },[inputComment, postId, createdAt, addLikes])

    const handleChange = e => {
      setInputValue(e.target.value);
    };
    const postComment = e => {
      e.preventDefault();
      const newComment = {
        postId: postId,
        id: uuidv4(),
        username: faker.name.findName(),
        text: inputValue
      };
      setInputComment([...inputComment, newComment]);
      setInputValue("");
      setCreatedAt(moment(new Date(), "MMM D LTS").fromNow());
    };
    const handleLikes = () => {
      let newLike = likes;
      updateLikes(newLike + 1);
    };


    return (
      <PostContainerStyle display={show}>
        <UserDeets>
          <UserThumbnail src={thumbnailUrl} alt="user-profile" />
          <p>{username}</p>
        </UserDeets>
        <UserPostArea>
          <PostImage src={imageUrl} alt="user-post" />
        </UserPostArea>
        <Reaction>
          <PostIcons>
            <span onClick={handleLikes}>
              <IoIosHeartEmpty />
            </span>

            <span>
              <FaRegComment />
            </span>
          </PostIcons>
          {addLikes} likes
        </Reaction>
        {inputComment.map(comment => {
          return <CommentSection key={comment.id} props={comment} />;
        })}
        <TimeStamp>{createdAt}</TimeStamp>
        <Form
          inputValue={inputValue}
          changeHandler={handleChange}
          addComment={postComment}
        />
      </PostContainerStyle>
    );
};

export default PostContainer;

解釋

  • Note 在我們的 PostContainer 組件中,我們從 App.js 收到的 props 使用 useState 呈現為狀態 掛鉤。
onst commentDate = timestamp.replace(/th/, "");
    const [inputValue, setInputValue] = useState("");
    const [inputComment, setInputComment] = useState(comments);
    const [createdAt, setCreatedAt] = useState(
      moment(new Date(commentDate), "MMM D LTS").fromNow()
    );

    const [addLikes, updateLikes] = useState(likes);
  • 我們還使用了 useEffect 鉤子來管理有狀態的邏輯並將我們的狀態更新持久化到 localStorage .

useEffect(()=>{
      const post = JSON.parse(localStorage.getItem("posts"));
      const postUpdate = post.map((userPost) => {
        if(postId === userPost.postId) {
          return {
            ...userPost, comments: inputComment, timestamp: `${moment(new Date(), "MMM D LTS")}`, likes: addLikes
          }
        }
        return userPost;
      });
      localStorage.setItem("posts", JSON.stringify(postUpdate));
    },[inputComment, postId, createdAt, addLikes])

useEffect 上面的鉤子注意第二個參數,它是一個可以觸發 useEffect 的狀態數組 功能。


[inputComment, postId, createdAt, addLikes]

這意味著對任何這些狀態的任何更改都會導致狀態在 localStorage 中更新 .

此時,我們的帖子應該像這樣在瀏覽器上呈現:

  • handleChange 函數調用 setInpuValue 處理表單輸入字段狀態的函數,就像 this.setState 類組件的方法。而 handleLikes 函數調用 updateLike 添加喜歡的功能

  • postComment 通過調用 setComment 為每個帖子添加評論並更新日期 和 setCreatedAt 分別發揮作用。

哇!那不是很有趣。現在我們可以 Add commentsAdd Likes 並堅持我們對 localStorage 的更改

是時候處理我們的 Login 組件並為 authentication 創建更高階的組件了

登錄.js

const Login = ({ props }) => {
    const [userInput, setUserInput] = useState({
      username: "",
      password: ""
    });
    const [loggedIn, setloggedIn] = useState(false);

    useEffect(() => {
      setloggedIn(true);
    }, [userInput.username, userInput.password]);
    const loginHandler = () => {
      let logDeets = {
        username: userInput.username,
        password: userInput.password,
        loggedIn: loggedIn
      };
      localStorage.setItem("User", JSON.stringify(logDeets));
    };

    const handleUserNameChange = e => {
      e.persist();
      const target = e.target;
      const value = target.value;
      const name = target.name;
      setUserInput(userInput => ({ ...userInput, [name]: value }));
      console.log(userInput);
    };
    return (
      <Container>
      <Form onSubmit={e => loginHandler(e)}>
      <Header>Instagram</Header>
        <FormInput
          placeholder="Phone number, username or email"
          name="username"
          type="text"
          value={userInput.username}
          onChange={handleUserNameChange}
        />
        <FormInput
          placeholder="Password"
          name="password"
          type="password"
          value={userInput.password}
          onChange={handleUserNameChange}
        />
        <SubmitBtn type="submit" value="Log In" />
      </Form>
      </Container>
    );
  };

export default Login;

Notice how we passed in an object as the useState() argument and how we destructured the state in the setUserInput() function

要添加一些身份驗證功能,我們需要創建一個 HOC(高階組件)。
高階組件是接收組件作為參數並返回帶有附加數據和功能的組件的組件。它們是零副作用的純函數。本項目中使用的 HOC 就是管理我們的組件渲染。

我們將首先在 authentication 中創建一個 js 文件 文件夾和我們的 PostContainer 中的另一個 零件

touch src/component/PostContainer/PostPage.js src/component/authentication/Authenticate.js

現在我們將進行一些代碼重構。在我們的 App.js 文件中,我們將剪切 SearchBar 組件和 PostContainer 組件並將其粘貼到我們的 PostPage.js 文件中。

PostPage.js

import React from 'react';
import SearchBar from "../SearchBar/SearchBar";
import PostContainer from './PostContainer';


const PostPage = ({
    handleSearch,
    search,
    posts
}) => {
    return (
        <div>
            <SearchBar search={search} handleSearch={handleSearch} />
      {posts.map((userPost, index) => {
        return <PostContainer 
        key={index} 
        props={userPost} 

        />;
      })}
        </div>
    );
}

export default PostPage;

然後我們的 App.js 文件


  return (
    <AppContainer>
    <ComponentFromWithAuthenticate
        handleSearch={handleSearch}
        search={search}
        posts={posts}
      />
    </AppContainer>
  );

export default App;

然後在我們的 Authenticate.js 文件中,我們輸入以下內容

import React from 'react';

const Authenticate = (WrappedComponent, Login)  => class extends React.Component {
    render() {
      let viewComponent;
      if (localStorage.getItem("User")) {
        viewComponent = <WrappedComponent {...this.props}/>
      } else {
        viewComponent = <Login />
      }
      return (
        <div className="container">
          {viewComponent}
        </div>
      )
    }
  }

  export default Authenticate; 

我們的小項目到此結束。

雖然我們只使用了 useStateuseEffect 鉤子(這是基本的和最廣泛使用的鉤子),您可以在 react 文檔中閱讀有關其他反應鉤子及其用途的信息。

項目鏈接

完整的項目 repo 和託管應用程序的鏈接可以在下面找到:

Instagram-克隆

Instagram-克隆-netlify

資源

反應文檔
湯姆鮑登
詹姆斯·金


Tutorial JavaScript 教程
  1. Angular:ActivatedRoute 不會在路由更改時更新子路由數據

  2. 在 React.js 中獲取 API 數據的 5 種方法

  3. 以隨機順序顯示博客廣告位

  4. JavaScript 代碼每日挑戰 #4

  5. 所有代碼在節點中運行時找不到節點:fs

  6. Deno v1.0 已發布! JavaScript 運行時而不是 Node.js 的新選項

  7. 2011 年 9 月的 10 個新 jQuery 插件

  1. 如何使用 React 創建密碼生成器

  2. Node Js 中的文件數據庫從頭開始第 2 部分:選擇函數及更多

  3. 55行js的簡單markdown解析器

  4. 災難性的回溯

  5. 使用 Node Version Manager 更新 Node 和 npm

  6. 使用 Google Home、IFTTT 和 Node-RED 控制小工具

  7. 深入了解 Vue 3 - 設置函數

  1. 捆綁 Angular 模塊

  2. 拋磚引玉 - 動力學排版第 2 部分:多虧了 matter.js 來欺騙重力! 📃🛫🤯

  3. N00b在這裡!我確定這是一個簡單的解決方法...嘗試通過 stackoverflow 安裝新的 webpack 但沒有運氣...

  4. 在 React 中快速輕鬆地構建一些東西