你会做出反应吗?——构建一个视频应用程序

2025-06-07

你会做出反应吗?——构建一个视频应用程序

介绍

什么是 React?

React 是由 Facebook 开源的流行 JavaScript 前端库。它允许你创建和复用 UI 元素。React 的诞生源于解决浏览器 DOM 速度慢的问题。它非常出名,并且被广泛使用。

为什么要使用 React?

可重用组件- React 是基于组件的,这意味着组件是 React UI 的构建块。组件描述了 UI 中一小部分的外观,并且可重用。复杂的应用程序可以通过将组件嵌套在其他组件中来构建。

SPA - React 的另一个重要特性是它是一个单页应用程序 (SPA)。这意味着当需要向用户显示不同的视图时,React 不会获取一个全新的网页,它只会使用从后端服务器获取的新数据重写当前网页。

使用虚拟 DOM 快速渲染- 更新 DOM 通常是 Web 性能的瓶颈。React 使用虚拟 DOM;即保存在内存中的 DOM。任何视图更改都会首先反映到虚拟 DOM,然后比较虚拟 DOM 的先前状态和当前状态,并仅对 DOM 应用必要且最小的更改。这正是 React高性能的主要原因

本文的主要目标-

  • 了解 React 如何将 HTML 渲染为 UI。
  • 了解如何使用 JSX。
  • 了解 React 组件、状态、道具和生命周期方法。
  • 从头开始创建一个 React 应用程序(视频)。

入门 -

首先,进入终端,然后输入 -

npx create-react-app videos
Enter fullscreen mode Exit fullscreen mode

此指令创建一个名为 videos 的 React 应用程序模板。
要在浏览器中的 localhost:3000 上运行您的应用程序,请在终端中输入-

cd videos
npm start
Enter fullscreen mode Exit fullscreen mode

它在浏览器中的样子如下-

浏览器中的输出

清理 -

打开 src 文件夹并删除以下文件 - App.test.js、logo.svg、setupTests.js
删除 App.js 和 Index.js 文件中的所有内容。

现在我们已经设置了项目并清理了不必要的文件,我们可以继续了解 React。

在 Index.js 文件中,放置以下内容-

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.querySelector('#root'));
Enter fullscreen mode Exit fullscreen mode

第 1 行和第 2 行:导入 react 和 react-dom 库。react 模块用于在 JavaScript(JSX)中编写 HTML 代码,而 react-dom 用于执行渲染函数,将内容显示到页面上。
第 3 行:导入 App 组件。
第 4 行:在 ID 为 root 的容器中渲染 App。

ReactDOM.render 方法-
此方法的作用是将 App 组件插入到浏览器的 DOM 中。它接收的第一个参数是需要渲染的 React 组件,第二个参数指定我们需要将组件插入到 public/index.html 文件中的哪个位置。您会<div>在 public/index.html 中找到一个 id 为 root 的元素。该<div>元素是 React 容器节点。首次调用 render() 函数时,整个容器节点将被替换为 App 组件。

现在我们将编写我们的 App 组件(转到 App.js 文件),但在此之前,让我们先了解一下基础知识 -

JSX - React 支持使用 JSX(JavaScript XML)的语句,这使得我们可以在 JavaScript 中使用类似 HTML 的语句来创建 UI 元素(组件)。你会发现 JSX 的语法非常易于使用,因为它与我们编写 HTML 的方式非常相似。

组件 - 如前所述,React 是基于组件的,组件是 React 中的构建块,一组元素组成一个组件。通常,每个组件都会定义导航栏、内容选项卡、登录表单等的外观。这也有助于将 UI 部分的视觉(元素)和逻辑(功能)部分整合在一起。

将我们的项目分解成组件 -

分解

该应用程序中将有 4 个组件。

  1. 搜索栏(位于顶部)
  2. 视频细节(左侧)
  3. 视频列表(右侧)
  4. VideoItem(VideoList 的项目)

功能组件-
定义组件的最简单方法是编写 JavaScript 函数。
在 app.js 中 -

import React from 'react';

function App(props) {
    return ();
}

Enter fullscreen mode Exit fullscreen mode

这个函数是一个有效的 React 组件,因为它接受一个带有数据的“props”对象参数,并返回组件渲染时需要渲染的元素。我们称这样的组件为“函数式组件”,因为它们本质上就是 JavaScript 函数。

基于类的组件-
创建 React 组件时,组件名称必须以大写字母开头。组件必须包含 extends React.Component 语句,该语句创建对 React.Component 的继承,并允许组件访问 React.Component 的函数。React 组件至少必须包含 render() 函数。render() 函数返回组件渲染时要渲染的元素。

要呈现组件“Component”,请使用<Component />标签。

对于我们的App组件(App.js)
这相当于上面的功能组件。

import React from 'react';

class App extends React.Component {
  render() {
    return ();
  }
}

Enter fullscreen mode Exit fullscreen mode

在类组件中管理状态
- 与特定组件相关的数据可以存储为其“状态”。组件的状态只能从该特定组件进行更改。所有状态变量都state以键值对的形式存储在类变量中。
每当状态发生变化时,React 都会重新渲染 UI。

修改状态-

  1. 不要直接修改状态,因为这不会重新渲染组件。
  2. 改为使用setState()。当 setState() 被调用时,React 知道状态已更改,并再次调用 render() 方法来了解屏幕上应该显示的内容。例如:
state = {selectedVideo: null}

//Wrong
this.state.selectedVideo = 'Something'

//Correct
this.setState({ selectedVideo: 'Something' })

Enter fullscreen mode Exit fullscreen mode

在 JSX 语法中,我们还可以通过用括号 (“{}”) 包裹正则 JavaScript 表达式来插入它们。
例如:


<h1>{ 2 + 5 * 8 }</h1>

<p> Current Selected Video is : {this.state.selectedVideo} </p>

Enter fullscreen mode Exit fullscreen mode

请注意,早期状态只能在类组件中使用,而不能在函数式组件中使用。因此,函数式组件也被称为无状态组件。
然而,在引入 React Hooks 之后,状态现在可以在类组件和函数式组件中使用。
在本项目中,我们没有使用 React Hooks,因此,如果需要状态变量,我们将使用类组件,否则我们将使用简单的函数组件。

继承属性的props
- 假设用户选择了某个视频,并将其存储在 this.state.SelectedVideo 状态变量中。为了在 VideoDetail 组件中显示该视频,我们必须将所选视频的信息传递给 VideoDetail 组件。props 或属性允许我们将数据从父组件传递到子组件。我们可以通过类似于 HTML 属性的方式将“props”值传递给单个组件。

<VideoDetail video={this.state.selectedVideo} />
Enter fullscreen mode Exit fullscreen mode

video是此处定义的 prop,包含 selectedVideo 数据。然后,我们可以像给函数传递参数一样,通过 props 传递数据:

const VideoDetail = (props) => {
   // code
}
Enter fullscreen mode Exit fullscreen mode

最后,我们使用点符号来访问 prop 数据并将其用作“props.video”

在类组件中,我们可以这样访问属性this.props.video

请注意,JSX 的元素只是 JavaScript 对象。这意味着 JSX 元素的 props 和 children 可以是任何可以放入 JavaScript 变量的内容——它们可以是字符串、状态变量、其他组件,甚至是函数。您将在项目中看到如何将不同的内容作为 props 传递给组件。

生命周期方法-
React 组件实例的生命周期包含三个阶段。
挂载 - 创建并插入 DOM。
更新 - 由于状态值变化而更新或重新渲染。
卸载 - 从 DOM 中移除。
每个生命周期阶段都涉及执行一组生命周期方法。

1. componentDidMount() - 在组件安装后立即调用,可用于执行需要 DOM 节点到位的初始化。在这里我们可以执行从 API 获取数据等任务。

2. componentDidUpdate() - 组件的任何 props 或 state 值发生变化时,都会更新/重新渲染。componentDidUpdate() 在组件更新后立即调用。它可以用于实现重新渲染后执行的任何逻辑。

还有其他生命周期方法,本文就不一一解释了。在本项目中,我们只会使用 componentDidMount() 。

好的!!

现在我们已经解决了这个问题,是时候开始行动了!

重要提示- 我们将使用语义 UI 来设置组件的样式。(语义 UI 是一个开发框架,它使用人性化的 HTML 来帮助创建美观、响应式的布局。)为此,请在 public/index.html 的 head 中添加以下代码 -


<link rel = 'stylesheet' href = "https://cdnjs.cloudflare.com/ajax/libs/semanticui/2.4.1/semantic.min.css"
integrity = "sha512-8bHTC73gkZ7rZ7vpqUQThUDhqcNFyYi2xgDgPDHc+GXVGHXq+xPjynxIopALmOPqzo9JZj0k6OqqewdGO3EsrQ==" crossorigin = "anonymous" />

项目文件夹结构 -

项目结构

另外,请注意,本文主要关注 React 概念。因此,请忽略不相关的主题。

index.js -`

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(<App />, document.querySelector('#root'));
Enter fullscreen mode Exit fullscreen mode

apis/youtube.js

import axios from 'axios';

// create your youtube api key and place it here
const KEY = YOUR_API_KEY;

// setting up base instance in which you can define a URL and
// any other configuration elements.
// and exporting as default.

export default axios.create({
    baseURL: 'https://www.googleapis.com/youtube/v3',
    params: {
        part: 'snippet',
        maxResults: 5,
        key: KEY
    }
});

// we can import this instance from other files and no longer need to write 
// whole URL everytime we call API
Enter fullscreen mode Exit fullscreen mode

VideoItem.js

import './VideoItem.css';
import React from 'react';

// takes props video object and onVideoSelect function
// we call onVideoSelect when user clicks the video

// What does a VideoItem Component contains?
// yepp, Video Thumbnail and Video title

const VideoItem = ({ video, onVideoSelect }) => {
    return (
        <div onClick={() => onVideoSelect(video)} className="item video-item">
            <img alt={video.snippet.title} className="ui image" src={video.snippet.thumbnails.medium.url} />
            <div className="content">
                <div className="header">{video.snippet.title}</div>
            </div>
        </div>
    );
}

export default VideoItem;
Enter fullscreen mode Exit fullscreen mode

视频项目.css

.video-item{
    display: flex !important;
    align-items: center !important;
    cursor: pointer;
}

.video-item.item img{
    max-width: 180px;
}
Enter fullscreen mode Exit fullscreen mode

视频列表.js

import React from 'react';
import VideoItem from './VideoItem';

// takes videos (video array) and onVideoSelect function
// What does a VideoList contain?
// yepp, List of Video (specifically VideoItems)
// So we iterate over videos array and make a VideoItem for each
// Note We are passing video and OnVideoSelect as props to VideoItem

const VideoList = ({ videos, onVideoSelect }) => {

    const renderedList = videos.map((video) => {
        return <VideoItem key={video.id.videoId} onVideoSelect={onVideoSelect} video={video} />;
    })

    return <div className="ui relaxed divided list">{renderedList}</div>;

}

export default VideoList;
Enter fullscreen mode Exit fullscreen mode

视频细节.js

import React from 'react';

// videoDetail takes the selectedVideo and displays its info.

const VideoDetail = ({ video }) => {
    if (!video) {
        return <div>Loading...</div>;
    }

    //This url is for fetching selectedVideo
    const videoSrc = `https://www.youtube.com/embed/${video.id.videoId}`;

    return (
        <div>
            <div className="ui embed">
                <iframe title="video player" src={videoSrc} />
            </div>
            <div className="ui segment">
                <h4 className="ui header">{video.snippet.title}</h4>
                <p>{video.snippet.description}</p>
            </div>
        </div>
    );
}

export default VideoDetail;
Enter fullscreen mode Exit fullscreen mode

SearchBar.js

import React from 'react';

// state variable "term" stores what user types in searchBar

class SearchBar extends React.Component {
    state = { term: '' };

    // this sets the "term" to what user types in. (in sync)
    onInputChange = (e) => {
        this.setState({ term: e.target.value });
    }

    // it is called when user submits the "term"
    // which in turn calls the onTermSubmit() function passed as its prop
    onSearchBarSubmit = (e) => {
        e.preventDefault();
        this.props.onTermSubmit(this.state.term);
    }

    render() {
        return (
            <div className="ui segment search-bar" style={{ marginTop: '20px' }}>
                <form onSubmit={this.onSearchBarSubmit} className="ui form">
                    <div className="field">
                        <label>Video Search</label>
                        <input
                            style={{ backgroundColor: 'whitesmoke' }}
                            type="text" value={this.state.term}
                            onChange={this.onInputChange}
                        />
                    </div>
                </form>
            </div>
        );
    }
}

export default SearchBar;
Enter fullscreen mode Exit fullscreen mode

App.js

import React from 'react';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
import youtube from '../apis/youtube';
import VideoDetail from './VideoDetail';

class App extends React.Component {
    state = { videos: [], selectedVideo: null };
    // videos - array of videos based on term that user passed in searchbar (initally empty)
    // selectedVideo - video selected to display on left 


    // this lifecycle method is called when App component gets mounted 
    componentDidMount() {
        this.onTermSubmit('dev.to');
    }
    // Note that here 'dev.to' is initial term for which videos will be searched
    // It is random



    // This function is the one that accepts the term and fetches videos
    // and set "videos" state variable to fetched videos and
    // selectedVideo to the first video of videos
    onTermSubmit = async (term) => {
        const response = await youtube.get('/search', {
            params: {
                q: term
            }
        });
        this.setState({ videos: response.data.items, selectedVideo: response.data.items[0] });
    }

    onVideoSelect = (video) => {
        this.setState({ selectedVideo: video });
    };

    render() {
        return (
            <div className="ui container" style={{ backgroundColor: 'whitesmoke', padding: '40px' }}>
                <SearchBar onTermSubmit={this.onTermSubmit} />
                <div className="ui grid">
                    <div className="ui row">
                        <div className="eleven wide column">
                            <VideoDetail video={this.state.selectedVideo} />
                        </div>
                        <div className="five wide column">
                            <VideoList onVideoSelect={this.onVideoSelect} videos={this.state.videos} />
                        </div>
                    </div>
                </div>
            </div>
        );
    }

}

export default App;
Enter fullscreen mode Exit fullscreen mode

结论

在本文中,我们学习了ReactJS的基本概念以及如何在实际项目中应用这些概念。现在你已经对 React 有了基本的了解,可以继续学习更高级的主题,例如 Hooks、Redux 等。

感谢阅读

这是我在 Dev 上的第一篇文章。希望你们喜欢,不要觉得太过冗长。如果你们喜欢这篇文章,请点赞❤️。祝你们编程愉快😊。

文章来源:https://dev.to/jaskaran/fundamentals-of-react-build-a-video-app-24j8
PREV
MERN 的“N”和“E”——Node.js 和 Express.js Express 的基础知识
NEXT
像我五岁一样解释 React.js 中的高阶组件(HOC)