JavaScript >> Javascript 文檔 >  >> React

反應式 Web 堆棧:3RES – React、Redux、RethinkDB、Express、Socket.io

這篇文章由 Scott Hasbrouck 撰寫。你可以在 Twitter 或他的網站上找到他。

根據 StackOverflow 的數據,過去幾年 JavaScript 在 Web 技術中真正火起來,最終成為 2016 年最常用的語言,真是太棒了。我使用 JavaScript 的歷史大約在 15 年前開始,在 1996 年它作為 Netscape Navigator 2 的一部分首次發布之後不久。我最常用的學習資源是 DynamicDrive,以及他們的教程和“動態 HTML”或 DHTML 代碼片段– 一個由 Internet Explorer 4 創造的術語。實際上,DHTML 是一組使用 JavaScript、CSS 和 HTML 實現的瀏覽器功能,可以為您提供漂亮的元素,例如滾動按鈕和股票行情。

快進到今天,我們現在生活在一個 JavaScript 已經發展到接管 Web 技術的世界。不僅在瀏覽器中,而且根據同一份 StackOverflow 報告,它現在是最流行的後端語言!自然,總是有人不喜歡這種語言,理由是創建全局變量很容易,或者 null 是一個對象,而 undefined 是它自己的數據類型。但我發現我學習的每一種語言都有一些怪癖,一旦你學會了正確使用它,這些怪癖很容易避免。我們確實想成為我們的工藝專家,真正學會掌握我們的工具,不是嗎?

以下是我認為為什麼的主要因素(好與不好) JavaScript 如此迅速地佔領了互聯網:

  1. JavaScript 是唯一 通用客戶端語言。
  2. JavaScript 相對容易學習,尤其是來自任何其他類似 C 的語言。
  3. 隨著 Node.js 的出現,JavaScript 現在可以在服務器上運行(而 Node/V8 這樣做的資源效率極高)。
  4. ES6 的出現恰逢其時,“修復”了 JavaScript 語法和功能缺失的許多問題。
  5. 成熟的前端框架。讓我們面對現實吧,用原生 JavaScript 構建前端應用程序需要大量的紀律來防止它變成意大利麵條式代碼。 React/Redux/Reflux 和 Angular 提供了框架以保持其井井有條。
  6. 開源項目的廣度和質量,以及使用 npm 安裝這些模塊的便利性。

特別是,Node.js 的出現將 JavaScript 的採用率推向了歷史新高。有了它,我們只需要為整個堆棧學習一種語言,並且能夠用它構建後台工作者和 HTTP 服務器之類的東西!我什至最近完成了我的第一本關於使用 JavaScript 和 Node.js 以條紋方式向信用卡收費的書——這是我十多年前第一次學習這門語言時從未想過自己能夠做到的。因此,無論您喜不喜歡,我們都生活在 JavaScript 互聯網世界中。 但你來了 .我的猜測是你可能喜歡它。太好了,歡迎!因為現在我想與你分享我是如何設法利用這個新的廣闊的 JavaScript 世界來構建一個真正的響應式 Web 應用程序堆棧——從上到下都使用一種語言。

3RES 堆棧

是啊,我也不知道怎麼發音……三分?當然。讓我們從 React 開始。

僅前端庫

反應

React 是一種構建用戶界面的聲明式方法,它在很大程度上依賴於其類似 XML 的語法擴展,稱為 JSX。您的應用程序是由“組件”構建的——每個組件都封裝了 UI 中通常可重用的小部分。這些組件都有自己的不可變狀態,其中包含有關組件應如何呈現的信息。 state 有一個純 setter 函數(沒有副作用),不應該直接改變。提議的 3RES 堆棧的概述只需要 React 的基本知識。當然,你想成為 React 大師!一定要在 SurviveJS 了解有關 React 的更多信息——最好的綜合性 React 書籍之一,免費版本。

還原

如果 React 封裝了你所有的 UI 組件,那麼 Redux 將你所有的數據封裝為一個 JavaScript 對象。此狀態對像是不可變的,不應直接修改,而只能通過調度操作來修改。這樣React/Redux結合起來就可以自動react 狀態更改,並更新相關的 DOM 元素以反映新值。 Redux 有一些很棒的文檔——可能是我用過的任何開源庫中最好的。最重要的是,Redux 在 Egghead 上還提供了 30 個免費視頻。

前端和後端庫

Socket.IO

迄今為止,您的 Web 應用程序很可能依賴 AJAX 與服務器進行通信——它是建立在 Microsoft 引入的稱為 XMLHttpRequest 的 JavaScript API 之上的。對於許多一次性用戶引發的操作,例如登錄,AJAX 很有意義。但是,依賴它來獲取持續更新的數據以及多個客戶端是非常浪費的。處理這個問題的唯一真正方法是定期輪詢後端,請求新數據。 WebSockets 是一種相對較新的技術,直到 2011 年才被標準化。WebSocket 會打開一個持續掛起的 TCP 連接,並允許 由服務器或客戶端發送的數據。它以 HTTP“握手”作為升級請求啟動。然而,類似於我們經常不使用 vanilla XMLHttpRequest API(相信我,我不得不這樣做,你不想自己實現它並支持每個瀏覽器),我們通常也不使用JavaScript WebSocket API 直接。 Socket.io 是客戶端和服務器端 WebSocket 通信最廣泛接受的庫,並且還為 WebSocket 失敗時實現 XMLHttpRequest/polling 回退。我們將把這個庫與 RethinkDB changefeeds(如下所述)和 Redux 結合使用,以不斷使我們所有客戶的狀態與我們的數據庫保持同步!

後端庫和技術

重新思考數據庫

RethinkDB 是一個存儲 JSON 文檔的開源 NoSQL 數據存儲。它經常與 MongoDB 進行比較,但在與使我們的 3RES 堆棧工作相關的許多關鍵方面都非常優越。首先,RethinkDB 通過查詢 changefeeds 開箱即用 – 將事件偵聽器附加到查詢的能力,該查詢將在添加、更新或刪除該查詢選擇的文檔時接收實時更新!如上所述,我們將從 RethinkDB 更改源中發出 Socket.io 事件。此外,RethinkDB 通過分片擴展非常簡單,並通過複製實現冗餘。它有一個令人驚嘆的開發者外展計劃和清晰的文檔,並且隨著像我們這樣的工程師的反饋不斷改進。

快遞

最後,我們的應用程序仍然需要接受 HTTP 請求作為路由。 Express 是公認的用於構建 HTTP 路由的簡約 Node.js 框架。我們將把它用於需要 Socket.io 範圍之外的一次性事件的所有事情:初始頁面加載、登錄、註冊、註銷等。

構建服務器代碼

我們的示例應用程序將是一個沒有身份驗證的簡單 Todo Checklist。我經常抱怨的一個問題是,當一個簡單教程的示例應用程序有一個龐大的代碼庫時——它只會讓挑選應用程序的相關部分變得太耗時。因此,此示例應用程序將非常小,但將準確顯示此堆棧的每個所需部分的一個示例,以實現端到端反應性。唯一的文件夾是 /public 包含我們所有構建的 JavaScript 的文件夾。這個應用程序在這種精神中遺漏的一個重要點是身份驗證和會話 - 互聯網上的任何人都可以閱讀和編輯 Todo’s!如果你有興趣使用 Socket.io 和 Express 向這個應用程序添加身份驗證,我的網站上有一個完整的教程來說明如何做到這一點!

讓我們從後端開始。首先,您需要獲取 RethinkDB 的副本,然後使用以下命令啟動它:

[旁注]

閱讀博客文章很好,但觀看視頻課程更好,因為它們更具吸引力。

許多開發人員抱怨 Node.js 上缺乏負擔得起的高質量視頻材料。觀看 YouTube 視頻會讓人分心,花 500 美元購買 Node 視頻課程很瘋狂!

去看看 Node University,它有關於 Node 的免費視頻課程:node.university。

[旁注結束]

$ rethinkdb

啟動 RethinkDB 後,導航到位於 http://localhost:8080 的超級方便的 Web 界面。單擊頂部的“表”選項卡,然後添加一個名為“3RES_Todo”的數據庫,然後在創建後添加一個名為“Todo”的表。

此示例的完整代碼在 Github 上,因此我們將在這裡介紹關鍵點,假設您熟悉 Node.js 基礎知識。 repo 包含 package.json 中的所有必需模塊 ,但如果您想手動安裝應用後端部分所需的模塊,請運行:

$ npm install --save rethinkdb express socket.io

現在我們有了所需的包,讓我們設置一個基本的節點應用程序,它服務於 index.html .

// index.js

// Express
var express = require('express');
var app = express();
var server = require('http').Server(app);
var path = require('path');

// Socket.io
var io = require('socket.io')(server);

// Rethinkdb
var r = require('rethinkdb');

// Socket.io changefeed events
var changefeedSocketEvents = require('./socket-events.js');

app.use(express.static('public'));

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname + '/index.html'));
});

r.connect({ db: '3RES_Todo' })
.then(function(connection) {
    io.on('connection', function (socket) {

        // insert new todos
        socket.on('todo:client:insert', function(todo) {
            r.table('Todo').insert(todo).run(connection);
        });

        // update todo
        socket.on('todo:client:update', function(todo) {
            var id = todo.id;
            delete todo.id;
            r.table('Todo').get(id).update(todo).run(connection);
        });

        // delete todo
        socket.on('todo:client:delete', function(todo) {
            var id = todo.id;
            delete todo.id;
            r.table('Todo').get(id).delete().run(connection);
        });

        // emit events for changes to todos
        r.table('Todo').changes({ includeInitial: true, squash: true }).run(connection)
        .then(changefeedSocketEvents(socket, 'todo'));
    });
    server.listen(9000);
})
.error(function(error) {
    console.log('Error connecting to RethinkDB!');
    console.log(error);
});

除了您可能已經看過一百次的幾行樣板 Express/Node.js,您會注意到的第一個新事物是與 RethinkDB 的連接。 connect() 方法指定我們之前設置的“3RES_Todo”數據庫。建立連接後,我們會監聽來自客戶端的 Socket.io 連接,然後告訴 Express 監聽我們想要的任何端口。連接事件反過來提供了我們從中發出事件的套接字。

現在我們有了一個 RethinkDB 連接和一個到客戶端的 Socket,讓我們在 RethinkDB 的“Todo”表上設置 changefeed 查詢! changes() 方法接受屬性的對象文字,我們將使用兩個: includeInitial 屬性告訴 RethinkDB 將整個表作為第一個事件發送,然後監聽變化。 squash 屬性將確保將同時發生的更改合併到一個事件中,以防兩個用戶同時更改 Todo。
在啟動 RehtinkDB 更改源之前偵聽 Socket.io 事件,允許我們按用戶修改查詢.例如,在現實世界的應用程序中,您可能希望為該特定用戶會話廣播待辦事項,因此您可以將 userId 添加到您的 RethinkDB 查詢中。如前所述,如果您想了解如何在 Socket.io 中使用會話,我的博客上有完整的文章。

接下來,我們為客戶端引發的事件註冊三個套接字事件偵聽器:插入、更新和刪除。這些事件依次進行必要的 RethinkDB 查詢。

最後,您將看到 changefeed 調用我們正在導入的函數。這個函數接受兩個參數:套接字引用,以及我們想在我們的套接字中調用這些單獨行的字符串(在本例中為“todo”)。這是發出 Socket.io 事件的 changefeed 處理函數:

// socket-events.js

module.exports = function(socket, entityName) {
    return function(rows) {
        rows.each(function(err, row) {
            if (err) { return console.log(err); }
            else if (row.new_val && !row.old_val) {
                socket.emit(entityName + ":insert", row.new_val);
            }
            else if (row.new_val && row.old_val) {
                socket.emit(entityName + ":update", row.new_val);
            }
            else if (row.old_val && !row.new_val) {
                socket.emit(entityName + ":delete", { id: row.old_val.id });
            }
        });
    };
};

如您所見,傳入 socket 參考和entityName ,返回一個函數,該函數接受來自 RethinkDB 的行游標。所有 RethinkDB 游標都有一個 each() 方法,可用於逐行遍歷游標。這使我們能夠分析 new_valold_val 每一行,然後通過一些簡單的邏輯,我們判斷每一個變化是否是一個insert , update , 或 delete 事件。然後將這些事件類型附加到 entityName 字符串,以產生映射到實體本身的對象的事件,例如:

'todo:new' => { name: "Make Bed", completed: false, id: ''48fcfafa-a2fa-454c-9ab4-8a5540d07ee0'' }

'todo:update' => { name: "Make Bed", completed: true, id: ''48fcfafa-a2fa-454c-9ab4-8a5540d07ee0'' }

'todo:delete' => { id: ''48fcfafa-a2fa-454c-9ab4-8a5540d07ee0'' }

最後,為了試試這個,讓我們用一些能夠監聽這些事件的簡單 JavaScript 創建一個 index.html 文件:

<html>
    <head>
        <script src="/socket.io/socket.io.js"></script>
        <script>
            var socket = io.connect('/');
            socket.on('todo:insert', function (data) {
                console.log("NEW");
                console.log(data);
            });
            socket.on('todo:update', function (data) {
                console.log("UPDATE");
                console.log(data);
            });
            socket.on('todo:delete', function (data) {
                console.log("DELETE");
                console.log(data);
            });
        </script>
    </head>
    <body>Checkout the Console!</body>
<html>

讓我們試一試吧!轉到您的終端(假設您仍在另一個選項卡中運行 RethinkDB),然後運行:

$ node index.js

在 Chrome 中打開兩個選項卡:http://localhost:9000 和 http://localhost:8080。在帶有我們簡單節點應用程序的選項卡中,打開您的 JavaScript 控制台,您會注意到那裡什麼都沒有——因為我們還沒有添加任何 Todo!現在在 Chrome 的端口 8080 選項卡中打開 RethinkDB 控制台,導航到 Data Explorer 選項卡,然後運行以下查詢:

r.db("3RES_Todo").table("Todo").insert({ name: "Make coffee", completed: false })

現在使用 Node 應用程序返回到您的另一個 Chrome 選項卡。中提琴!這是我們剛剛添加到數據庫中的待辦事項,明確標識為新記錄。現在嘗試使用 RethinkDB 分配給您的 todo 的 id 更新 todo:

r.db("3RES_Todo").table("Todo").get("YOUR_TODO_ID").update({ completed: true })

再一次,更改事件被識別為更新,並且新的 todo 對像被推送到我們的客戶端。最後,讓我們刪除待辦事項:

r.db("3RES_Todo").table("Todo").get("YOUR_TODO_ID").delete()

我們的 changefeed 處理程序將其識別為刪除事件,並返回一個僅包含 id 的對象(以便我們可以在 Redux 狀態下將其從待辦事項數組中刪除!)。

這完成了後端將待辦事項和更改實時推送到我們的前端所需的一切。讓我們繼續討論 React/Redux 代碼以及如何將這些套接字事件與 Redux 調度程序集成。

基本的 React Todo 應用程序

首先,讓我們設置前端需求並與 WebPack 捆綁。首先,安裝所需的模塊(如果你已經下載了 repo 並運行 npm install 你不需要這樣做):

$ npm install --save react react-dom material-ui react-tap-event-plugin redux react-redux
$ npm install --save-dev webpack babel-loader babel-core babel-preset-es2015 babel-preset-react babel-plugin-transform-class-properties

現在讓我們設置 Webpack,我們的 webpack.config.js 還應該包括 babel,以及 babel transform-class-properties 插件:

var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: './components/index.jsx',
    output: { path: __dirname + '/public', filename: 'bundle.js' },
    module: {
        loaders: [{
            test: /.jsx?$/,
            loader: 'babel-loader',
            exclude: /node_modules/,
            query: {
                presets: ['es2015', 'react'],
                plugins: ['transform-class-properties']
            }
        }]
    }
}

我們都準備好開始構建 React/Redux 前端應用程序了!如果你需要復習 React 和/或 Redux,介紹中提到的資源會有所幫助。讓我們去掉 index.html 中的代碼來演示 Socket.IO 是如何工作的,添加一些字體,在一個空 div 上放置一個 id,我們可以將 React 應用程序附加到,然後導入 webpack 包:

<html>
    <head>
        <link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500' rel='stylesheet' type='text/css'>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
        <script src="/socket.io/socket.io.js"></script>
    </head>
    <body style="margin: 0px;">
        <div id="main"></div>
        <script src="bundle.js"></script>
    </body>
<html>

讓我們將所有的 React 渲染和一些其他設置放在 components/index.js 中 :

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from '../stores/todos.js';

import App from './app.jsx';

// Setup our socket events to dispatch
import TodoSocketListeners from '../socket-listeners/todos.js';
TodoSocketListeners(store);

// Needed for Material-UI
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

// Render our react app!
ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('main'));

請注意,我們必須為 Material-UI 導入一個煩人的點擊事件監聽器(看起來他們正在努力消除這個要求)。導入根 App 組件後,我們在 /socket-listeners/todos.js 中導入一個調度 Redux 動作的套接字事件監聽器 :

// socket-listeners/todos.js
import io from 'socket.io-client';
const socket = io.connect('/');

export default function(store) {
    socket.on('todo:insert', (todo) => {
        store.dispatch({
            type: 'todo:insert',
            todo: todo
        });
    });

    socket.on('todo:update', function (todo) {
        store.dispatch({
            type: 'todo:update',
            todo: todo
        });
    });

    socket.on('todo:delete', function (todo) {
        store.dispatch({
            type: 'todo:delete',
            todo: todo
        });
    });
}

這個函數相當簡單。我們所做的只是監聽後端發出的套接字事件 socket-events.js .然後調度插入、更新或刪除的待辦事項,這些待辦事項又由 RethinkDB 變更源觸發。這將所有 RehtinkDB/Socket 魔法聯繫在一起!

現在讓我們構建構成應用程序的 React 組件。在 components/index.jsx 中導入 ,讓我們製作 components/app.jsx

import React from 'react';
import AppBar from 'material-ui/lib/app-bar';
import TodoList from './todoList.jsx';
import AddTodo from './addTodo.jsx';

import { connect } from 'react-redux';

class Main extends React.Component {
    render() {
        return (<div>
            <AppBar title="3RES Todo" iconClassNameRight="muidocs-icon-navigation-expand-more" />
            <TodoList todos={this.props.todos} />
            <AddTodo />
        </div>);
    }
}

function mapStateToProps(todos) {
    return { todos };
}

export default connect(mapStateToProps)(Main);

這都是 React 和 React-Redux 的樣板。我們導入 connect 來自 react-redux ,並將狀態映射到 TodoList 組件的 props,即 components/todoList.jsx

import React from 'react';
import Table from 'material-ui/lib/table/table';
import TableBody from 'material-ui/lib/table/table-body';
import Todo from './todo.jsx';

export default class TodoList extends React.Component {
    render() {
        return (<Table>
            <TableBody>
                {this.props.todos.map(todo => <Todo key={todo.id} todo={todo} /> )}
            </TableBody>
        </Table>);
    }
}

待辦事項列表由 Material-UI 表組成,我們只是將待辦事項從道具映射到單獨的待辦事項組件:

import React from 'react';
import TableRow from 'material-ui/lib/table/table-row';
import TableRowColumn from 'material-ui/lib/table/table-row-column';
import Checkbox from 'material-ui/lib/checkbox';
import IconButton from 'material-ui/lib/icon-button';

// Import socket and connect
import io from 'socket.io-client';
const socket = io.connect('/');

export default class Todo extends React.Component {
    handleCheck(todo) {
        socket.emit('todo:client:update', {
            completed: !todo.completed,
            id: todo.id
        });
    };

    handleDelete(todo) {
        socket.emit('todo:client:delete', todo);
    };

    render() {
        return (<TableRow>
            <TableRowColumn>
                <Checkbox label={this.props.todo.name} checked={this.props.todo.completed} onCheck={this.handleCheck.bind(this, this.props.todo)} />
            </TableRowColumn>
            <TableRowColumn>
                <IconButton iconClassName="fa fa-trash" onFocus={this.handleDelete.bind(this, this.props.todo)} />
            </TableRowColumn>
        </TableRow>)
    }
}

單個 Todo 組件將 Socket.IO 事件的發射器附加到復選框和刪除按鈕的適當 UI 事件。這會將更新或刪除的 todo 發送到服務器中的 Socket 事件監聽器。

我們需要的最後一個 React 組件是添加待辦事項的按鈕!我們將在應用的右下角附加一個懸停的添加按鈕:

import React from 'react';
import Popover from 'material-ui/lib/popover/popover';
import FloatingActionButton from 'material-ui/lib/floating-action-button';
import ContentAdd from 'material-ui/lib/svg-icons/content/add';
import RaisedButton from 'material-ui/lib/raised-button';
import TextField from 'material-ui/lib/text-field';

// Import socket and connect
import io from 'socket.io-client';
const socket = io.connect('/');

export default class AddTodo extends React.Component {
    constructor(props) {
        super(props);
        this.state = { open: false };
    };

    handlePopoverTap = (event) => {
        this.setState({
            open: true,
            anchor: event.currentTarget
        });
    };

    handlePopoverClose = () => {
        this.setState({ open: false });
    };

    handleNewTaskInput = (event) => {
        if (event.keyCode === 13) {
            if (event.target.value && event.target.value.length > 0) {

                // Emit socket event for new todo
                socket.emit('todo:client:insert', {
                    completed: false,
                    name: event.target.value
                });

                this.handlePopoverClose();
            }
            else {
                this.setState({ error: 'Tasks must have a name'});
            }
        }
    };

    render() {
        return (<div>
            <Popover
                open = { this.state.open }
                anchorEl = { this.state.anchor }
                anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
                targetOrigin={{ horizontal: 'left', vertical: 'bottom' }}
                onRequestClose={this.handlePopoverClose}>
                <TextField
                    style={{ margin: 20 }}
                    hintText="new task"
                    errorText={ this.state.error }
                    onKeyDown={this.handleNewTaskInput} />
            </Popover>
            <FloatingActionButton onTouchTap={this.handlePopoverTap} style={{ position: 'fixed', bottom: 20, right: 20 }}>
                <ContentAdd />
            </FloatingActionButton>
        </div>)
    };
}

該組件的渲染方法包括添加按鈕,然後顯示帶有輸入字段的彈出框。彈出框根據布爾值 state.open 隱藏和顯示 .隨著輸入的每一次按鍵,我們調用 handleNewTaskInput ,它監聽 keycode 13(回車鍵)。如果輸入字段為空,則會顯示錯誤(改進說明:最好在後端驗證這一點)。如果輸入字段不為空,我們會發出新的 todo 並關閉彈出框。

現在,我們只需要更多樣板 Redux 來將所有這些聯繫在一起。首先,一個用於 todos 的 reducer,並將它們組合起來(提前計劃我們何時構建這個應用程序並擁有多個 reducer):

// reducers/todos.js

// todos reducer
const todos = (state = [], action) => {
    // return index of action's todo within state
    const todoIndex = () => {
        return state.findIndex(thisTodo => {
            return thisTodo && thisTodo.id === action.todo.id;
        });
    };

    switch(action.type) {
        case 'todo:insert':
            // append todo at end if not already found in state
            return todoIndex() < 0 ? [...state, action.todo] : state;

        case 'todo:update':
            // Merge props to update todo if matching id
            var index = todoIndex();
            if (index > -1) {
                var updatedTodo = Object.assign({}, state[index], action.todo);
                return [...state.slice(0, index), updatedTodo, ...state.slice(index + 1)]
            }
            else {
                return state;
            }

        case 'todo:delete':
            // remove matching todo
            var index = todoIndex();
            if (index > -1) {
                return [...state.slice(0, index), ...state.slice(index + 1)];
            }
            else {
                return state;
            }

        default:
            return state;
    }
};

export default todos;

並結合減速器:

// reducers/index.js

import { combineReducers } from 'redux';
import todos from './todos.js';

const todoApp = combineReducers({ todos });

export default todoApp;

reducer 有一個實用函數,用於檢查狀態中是否已經存在待辦事項(您會注意到,如果您保持瀏覽器窗口打開並重新啟動服務器,socket.IO 將再次向客戶端發出所有事件)。更新待辦事項使用 Object.assign() 返回一個帶有更新後的待辦事項屬性的新對象。最後,delete 使用 slice() – 它返回一個新數組,不像​​ splice() .

這些 reducer 的操作:

// actions/index.js

// Socket triggered actions
// These map to socket-events.js on the server
export const newTodo = (todo) => {
    return {
        type: 'todo:new',
        todo: todo
    }
}

export const updateTodo = (todo) => {
    return {
        type: 'todo:update',
        todo: todo
    }
}

export const deleteTodo = (todo) => {
    return {
        type: 'todo:delete',
        todo: todo
    }
}

讓我們把它們放在一起,用 webpack 構建它!

$ webpack --progress --colors --watch

我們的最終產品是一個漂亮、簡單的待辦事項應用程序,它可以對所有客戶端的所有狀態變化做出反應。並排打開兩個瀏覽器窗口,然後嘗試添加、檢查和刪除待辦事項。這是一個非常簡單的示例,說明了我如何將 RethinkDB 更改源、Socket.IO 和 Redux 狀態聯繫在一起,並且實際上只是觸及了可能的表面。身份驗證和會話真的會讓它成為一個非常棒的 web 應用程序。我可以想像一個可共享的待辦事項列表,供家庭、合作夥伴等用戶組使用。完成每個任務的事件源會立即更新給所有訂閱接收每個特定待辦事項組的用戶。

展望未來,我計劃做更多的工作,尋找一種更通用的方法,將 Redux 狀態中的任何對像數組綁定在一起,需要更少的樣板——一種連接的方法 一個 Socket.IO 端點的狀態數組,類似於 React-Redux 的 connect() .我很想听聽任何做過這件事的人的反饋,或者計劃在同一個堆棧中一起實施這些很棒的技術!

——

斯科特·哈斯布魯克

簡歷:Scott 是一名終身軟件工程師,他喜歡通過寫作和指導與他人分享他的技能。作為一名連續創業者,他目前是 ConvoyNow 的首席技術官,這是他作為技術創始人創辦的三家公司之一,引導了一到超過一百萬的用戶。他一直在通過遠足、駕駛小型飛機和旅行來尋找下一次冒險。

Convoy 是一個家庭技術支持解決方案!我們為遇到問題如何修復或使用他們的設備的客戶提供友好且知識淵博的技術支持專業人員。

這篇文章由 Scott Hasbrouck 撰寫。你可以在 Twitter 或他的網站上找到他。


Tutorial JavaScript 教程
  1. 是否有任何可公開訪問的 JSON 數據源來測試真實世界的數據?

  2. JavaScript 面試題 #46:JS 函數的長度

  3. 慢速 RabbitMq 生產者(負載測試)

  4. 使用 ajax 進行實時數據搜索。輸入為空時如何顯示另一個查詢[關閉]

  5. 在 WSL2 中安裝 asdf(ruby、nodejs 和 yarn)

  6. Array.reduce() 適合新手

  7. 在時間線上代表技術技能

  1. 我從測試 React 應用程序中學到了什麼——單元測試

  2. 使用 GraphQL 和 React 創建電影網站 - 第二部分

  3. 當單擊子錨點時,如何防止觸發父級的 onclick 事件?

  4. 在小吃博覽會中單擊停止按鈕時如何停止聲音?

  5. SaaS發布體驗

  6. 反應中的鏈接路由更改了鏈接,但頁面內容沒有任何變化

  7. 為初學者學習 JavaScript 的資源

  1. 任何 Web 框架中的性感、可訪問的顯示隱藏動畫

  2. 帶有動畫反饋的聯繫表

  3. 使用 Docker 和 Docker Compose 改進您的全棧應用程序開發

  4. 在 Angular 中創建一個簡單的麵包屑