react 对象克隆_如何使用React和Firebase创建Reddit克隆

react 对象克隆

React是一个很棒JavaScript库,用于构建用户界面。 创建React应用程序发布以来, 搭建准系统React应用程序变得非常容易。

在本文中,我们将结合使用Firebase和Create React App来构建功能类似于Reddit的应用程序。 它将允许用户提交新的链接,然后可以对其进行投票。

这是我们将要构建的实时演示

一位发明家将FireBase的心脏放入机器人创作中

为什么选择Firebase?

使用Firebase将使我们很容易向用户显示实时数据。 用户对链接进行投票后,反馈将立即生效。 Firebase的实时数据库将帮助我们开发此功能。 另外,它将帮助我们了解如何使用Firebase引导React应用程序。

为什么要React?

React因使用组件架构创建用户界面而闻名。 每个组件可以包含内部状态,也可以作为prop传递数据。 状态和道具是React中两个最重要的概念。 这两件事有助于我们在任何时间确定应用程序的状态。 如果您不熟悉这些术语,请先访问React文档

注意:您也可以使用ReduxMobX之类的状态容器,但是为了简单起见,在本教程中我们不会使用一个状态容器。

整个项目可在GitHub上找到

设置项目

让我们逐步完成设置项目结构和所有必要依赖项的步骤。

安装create-react-app

如果尚未安装,则需要安装create-react-app 。 为此,您可以在终端中键入以下内容:

npm install -g create-react-app

全局安装后,您可以使用它在任何文件夹中搭建一个React项目。

现在,让我们创建一个新的应用程序并将其称为reddit-clone

create-react-app reddit-clone

这将在reddit-clone文件夹中搭建一个新的create-react-app项目。 自举完成后,我们可以进入reddit-clone目录并启动开发服务器:

npm start

此时,您可以转到http:// localhost:3000 /并查看您的应用程序框架并开始运行。

构建应用程序

为了维护,我总是喜欢将容器组件分开。 容器是包含我们应用程序的业务逻辑并管理Ajax请求的智能组件。 组件只是愚蠢的呈现组件。 它们可以具有自己的内部状态,可以用来控制该组件的逻辑(例如,显示受控输入组件的当前状态)。

删除不必要的徽标和CSS文件后,这就是您的应用现在的外观。 我们创建了一个components文件夹和一个containers文件夹。 让我们将App.js移动到containers/App文件夹中,并在utils文件夹中创建registerServiceWorker.js

构建应用程序

您的src/containers/App/index.js文件应如下所示:

// src/containers/App/index.js

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div className="App">
        Hello World
      </div>
    );
  }
}

export default App;

您的src/index.js文件应如下所示:

// src/index.js

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

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

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

转到浏览器,如果一切正常,您将在屏幕上看到Hello World

您可以在GitHub上检查我的提交

添加React路由器

React-router将帮助我们定义应用程序的路由。 它是非常可定制的,并且在React生态系统中非常流行。

我们将使用3.0.0版本的react-router

npm install --save react-router@3.0.0

现在,使用以下代码在src文件夹中添加一个新文件routes.js

// routes.js

import React from 'react';
import { Router, Route } from 'react-router';

import App from './containers/App';

const Routes = (props) => (
  <Router {...props}>
    <Route path="/" component={ App }>
    </Route>
  </Router>
);

export default Routes;

Router组件包装所有Route组件。 基于Route组件的path属性,传递给该component属性的component将呈现在页面上。 在这里,我们正在设置根URL( / )以使用Router组件加载我们的App组件。

<Router {...props}>
  <Route path="/" component={ <div>Hello World!</div> }>
  </Route>
</Router>

上面的代码也是有效的。 对于路径/ ,将安装<div>Hello World!</div>

现在,我们需要从src/index.js文件中调用routes.js文件。 该文件应具有以下内容:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { browserHistory } from 'react-router';

import App from './containers/App';
import Routes from './routes';
import registerServiceWorker from './utils/registerServiceWorker';

ReactDOM.render(
  <Routes history={browserHistory} />,
  document.getElementById('root')
);

registerServiceWorker();

基本上,我们从routes.js文件中安装Router组件。 我们将history道具传递给它,以便路线知道如何处理历史跟踪

您可以在GitHub上检查我的提交

添加Firebase

如果您没有Firebase帐户,请立即访问其网站来创建一个帐户(免费!)。 创建新帐户后,登录到您的帐户并进入控制台页面,然后点击添加项目

输入项目的名称(我将其称为mine reddit-clone ),选择您的国家/地区,然后单击“ 创建项目”按钮。

现在,在继续之前,我们需要更改数据库规则 ,因为默认情况下,Firebase希望对用户进行身份验证才能读取和写入数据。 如果选择项目,然后单击左侧的“ 数据库”选项卡,则可以查看数据库。 您需要单击顶部的“规则”选项卡,这会将我们重定向到一个包含以下数据的屏幕:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

我们需要将其更改为以下内容:

{
  "rules": {
    ".read": "auth === null",
    ".write": "auth === null"
  }
}

这将使用户无需登录即可更新数据库。如果我们实现了在对数据库进行更新之前进行身份验证的流程,则需要Firebase提供的默认规则。 为了简化此应用程序,我们将不进行身份验证。

重要提示:如果不进行此修改,Firebase将不允许您从应用程序更新数据库。

现在,通过运行以下代码,将firebase npm模块添加到我们的应用中:

npm install --save firebase

接下来,将该模块导入您的App/index.js文件中,如下所示:

// App/index.js

import * as firebase from "firebase";

登录Firebase后选择项目时,将获得一个选项Add Firebase to your web app

将Firebase添加到您的Web应用程序

如果单击该选项,将出现一个模态,该模态将向我们显示将在componentWillMount方法中使用的config变量。

设定档

让我们创建Firebase配置文件。 我们将这个文件称为firebase-config.js ,它将包含将我们的应用程序与Firebase连接所需的所有配置:

// App/firebase-config.js

export default {
  apiKey: "AIzaSyBRExKF0cHylh_wFLcd8Vxugj0UQRpq8oc",
  authDomain: "reddit-clone-53da5.firebaseapp.com",
  databaseURL: "https://reddit-clone-53da5.firebaseio.com",
  projectId: "reddit-clone-53da5",
  storageBucket: "reddit-clone-53da5.appspot.com",
  messagingSenderId: "490290211297"
};

我们将Firebase配置导入App/index.js

// App/index.js

import config from './firebase-config';

我们将在constructor初始化Firebase数据库连接。

// App/index.js

constructor() {
  super();

  // Initialize Firebase
  firebase.initializeApp(config);
}

componentWillMount()生命周期挂钩中,我们使用刚刚安装的firebase包,并调用其initializeApp方法并将config变量传递给它。 该对象包含有关我们应用程序的所有数据。 initializeApp方法会将我们的应用程序连接到Firebase数据库,以便我们可以读取和写入数据。

让我们向Firebase添加一些数据以检查我们的配置是否正确。 转到数据库选项卡,然后将以下结构添加到数据库:

测试数据

单击添加会将数据保存到我们的数据库。

演示数据

现在,让我们向componentWillMount方法添加一些代码,以使数据显示在屏幕上:

// App/index.js

componentWillMount() {
  …

  let postsRef = firebase.database().ref('posts');

  let _this = this;

  postsRef.on('value', function(snapshot) {
    console.log(snapshot.val());

    _this.setState({
      posts: snapshot.val(),
      loading: false
      });
      });
    }

firebase.database()为我们提供了对数据库服务的引用。 使用ref() ,我们可以从数据库中获取特定的引用。 例如,如果调用ref('posts') ,我们将从数据库中获取posts引用,并将该引用存储在postsRef

每当数据库中有任何更改时postsRef.on('value', …)都会为我们提供更新后的值。 当我们需要基于任何数据库事件实时更新用户界面时,这非常有用。

使用postsRef.once('value', …)只会给我们一次数据。 这对于只需要加载一次且不会经常更改或需要主动监听的数据很有用。

on()回调中获取更新后的值之后,我们将这些值存储在posts状态中。

现在,我们将在控制台上看到数据。

样本数据

另外,我们会将这些数据传递给我们的孩子。 因此,我们需要修改App/index.js文件的render函数:

// App/index.js

render() {
  return (
    <div className="App">
      {this.props.children && React.cloneElement(this.props.children, {
        firebaseRef: firebase.database().ref('posts'),
        posts: this.state.posts,
        loading: this.state.loading
      })}
    </div>
  );
}

这里的主要目的是使post数据在我们所有的子组件中都可用,这将通过react-router传递。

我们正在检查this.props.children存在,如果存在,我们将克隆该元素并将所有props传递给所有孩子。 这是将道具传递给有活力的孩子的一种非常有效的方法。

调用cloneElement将浅合并在现有的道具this.props.children ,我们在这里通过的道具( firebaseRefpostsloading )。

使用此技术, firebaseRefpostsloading道具将可用于所有路线。

您可以在GitHub上检查我的提交

将应用程序与Firebase连接

Firebase只能将数据存储为对象。 它没有对数组的任何本机支持 。 我们将以以下格式存储数据:

数据库结构

手动在上面的屏幕截图中添加数据,以便您可以测试视图。

添加所有帖子的视图

现在,我们将添加视图以显示所有帖子。 创建具有以下内容的文件src/containers/Posts/index.js

// src/containers/Posts/index.js

import React, { Component } from 'react';

class Posts extends Component {
  render() {
    if (this.props.loading) {
      return (
        <div>
          Loading…
        </div>
      );
    }

    return (
      <div className="Posts">
        { this.props.posts.map((post) => {
            return (
              <div>
                { post.title }
              </div>
            );
        })}
      </div>
    );
  }
}

export default Posts;

在这里,我们只是映射数据并将其呈现到用户界面。

接下来,我们需要将其添加到我们的routes.js文件中:

// routes.js

…
<Router {...props}>
  <Route path="/" component={ App }>
    <Route path="/posts" component={ Posts } />
  </Route>
</Router>
…

这是因为我们希望帖子仅显示在/posts路线上。 因此,我们只将Posts组件传递给component prop,将/posts传递给react-router的Route组件的path prop。

如果转到URL localhost:3000 / posts ,将看到Firebase数据库中的帖子。

您可以在GitHub上检查我的提交

添加视图以撰写新帖子

现在,让我们从可以添加新帖子的位置创建一个视图。 创建具有以下内容的文件src/containers/AddPost/index.js

// src/containers/AddPost/index.js

import React, { Component } from 'react';

class AddPost extends Component {
  constructor() {
    super();

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  state = {
    title: ''
  };

  handleChange = (e) => {
    this.setState({
      title: e.target.value
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();

    this.props.firebaseRef.push({
      title: this.state.title
    });

    this.setState({
      title: ''
    });
  }

  render() {
    return (
      <div className="AddPost">
        <input
          type="text"
          placeholder="Write the title of your post"
          onChange={ this.handleChange }
          value={ this.state.title }
        />
        <button
          type="submit"
          onClick={ this.handleSubmit }
        >
          Submit
        </button>
      </div>
    );
  }
}

export default AddPost;

在这里, handleChange方法使用输入框中存在的值更新状态。 现在,当我们单击按钮时,将触发handleSubmit方法。 handleSubmit方法负责使API请求写入我们的数据库。 我们使用传递给所有孩子的firebaseRef道具来做到这一点。

this.props.firebaseRef.push({
  title: this.state.title
});

上面的代码块将标题的当前值设置到我们的数据库中。

新帖子存储在数据库中之后,我们再次将输入框设为空,准备添加新帖子。

现在,我们需要将此页面添加到我们的路线中:

// routes.js

import React from 'react';
import { Router, Route } from 'react-router';

import App from './containers/App';
import Posts from './containers/Posts';
import AddPost from './containers/AddPost';

const Routes = (props) => (
  <Router {...props}>
    <Route path="/" component={ App }>
      <Route path="/posts" component={ Posts } />
      <Route path="/add-post" component={ AddPost } />
    </Route>
  </Router>
);

export default Routes;

在这里,我们只是添加了/add-post路由,以便我们可以从该路由添加新的帖子。 因此,我们将AddPost组件传递给了它的组件prop。

另外,让我们修改src/containers/Posts/index.js文件的render方法,以便它可以遍历对象而不是数组(因为Firebase不存储数组)。

// src/containers/Posts/index.js

render() {
    let posts = this.props.posts;

    if (this.props.loading) {
      return (
        <div>
          Loading...
        </div>
      );
    }

    return (
      <div className="Posts">
        { Object.keys(posts).map(function(key) {
            return (
              <div key={key}>
                { posts[key].title }
              </div>
            );
        })}
      </div>
    );
  }

现在,如果我们转到localhost:3000 / add-post ,我们可以添加一个新帖子。 单击提交按钮后,新帖子将立即显示在帖子页面上

您可以在GitHub上检查我的提交

实施投票

现在,我们需要允许用户对帖子进行投票。 为此,让我们修改src/containers/App/index.jsrender方法:

// src/containers/App/index.js

render() {
  return (
    <div className="App">
      {this.props.children && React.cloneElement(this.props.children, {
        // https://github.com/ReactTraining/react-router/blob/v3/examples/passing-props-to-children/app.js#L56-L58
        firebase: firebase.database(),
        posts: this.state.posts,
        loading: this.state.loading
      })}
    </div>
  );
}

我们将firebase道具从firebaseRef: firebase.database().ref('posts')更改为firebase: firebase.database()因为我们将使用Firebase的set方法更新投票计数。 这样一来,如果我们有更多的火力地堡裁判,这将是很容易让我们仅使用处理它们firebase道具。

在进行投票之前,让我们handleSubmit修改一下src/containers/AddPost/index.js文件中的handleSubmit方法:

// src/containers/AddPost/index.js

handleSubmit = (e) => {
  …
  this.props.firebase.ref('posts').push({
    title: this.state.title,
    upvote: 0,
    downvote: 0
  });
  …
}

我们将我们的firebaseRef道具重命名为firebase道具。 因此,我们将this.props.firebaseRef.push更改为this.props.firebase.ref('posts').push

现在,我们需要修改src/containers/Posts/index.js文件以适应投票。

render方法应该修改为:

// src/containers/Posts/index.js

render() {
  let posts = this.props.posts;
  let _this = this;

  if (!posts) {
    return false;
  }

  if (this.props.loading) {
    return (
      <div>
        Loading...
      </div>
    );
  }

  return (
    <div className="Posts">
      { Object.keys(posts).map(function(key) {
          return (
            <div key={key}>
              <div>Title: { posts[key].title }</div>
              <div>Upvotes: { posts[key].upvote }</div>
              <div>Downvotes: { posts[key].downvote }</div>
              <div>
                <button
                  onClick={ _this.handleUpvote.bind(this, posts[key], key) }
                  type="button"
                >
                  Upvote
                </button>
                <button
                  onClick={ _this.handleDownvote.bind(this, posts[key], key) }
                  type="button"
                >
                  Downvote
                </button>
              </div>
            </div>
          );
      })}
    </div>
  );
}

单击按钮后,Firebase数据库中的upvotedownvote计数将增加。 为了处理该逻辑,我们创建了另外两个方法: handleUpvote()handleDownvote()

// src/containers/Posts/index.js

handleUpvote = (post, key) => {
  this.props.firebase.ref('posts/' + key).set({
    title: post.title,
    upvote: post.upvote + 1,
    downvote: post.downvote
  });
}

handleDownvote = (post, key) => {
  this.props.firebase.ref('posts/' + key).set({
    title: post.title,
    upvote: post.upvote,
    downvote: post.downvote + 1
  });
}

在这两种方法中,每当用户单击任一按钮时,相应的计数在数据库中都会增加,并在浏览器中立即更新。

如果我们使用localhost:3000 / posts打开两个选项卡,然后单击帖子的投票按钮,我们将看到每个选项卡几乎立即得到更新。 这是使用像Firebase这样的实时数据库的魔力。

您可以在GitHub上检查我的提交

存储库中 ,我已将/posts路由添加到应用程序的IndexRoute ,以默认显示在localhost:3000上的帖子。 您可以在GitHub上检查该提交

结论

公认的最终结果是有些准系统,因为我们没有尝试实现任何设计(尽管演示中添加了一些基本样式)。 我们也没有添加任何身份验证,以减少教程的复杂性和篇幅,但是显然任何实际应用程序都需要它。

Firebase在您不想创建和维护单独的后端应用程序的地方,或者您想要实时数据而又不花费太多时间开发API的地方确实有用。 正如您希望从本文中看到的那样,它在React中非常有效。

希望本教程对您将来的项目有所帮助。 请随时在下面的评论部分中分享您的反馈。

进一步阅读

本文由Michael Wanyoike进行同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/reddit-clone-react-firebase/

react 对象克隆

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值