GithubHelp home page GithubHelp logo

baidu / san-store Goto Github PK

View Code? Open in Web Editor NEW
59.0 18.0 35.0 1.58 MB

Application States Management for San

License: MIT License

JavaScript 99.34% HTML 0.20% CSS 0.09% EJS 0.24% Less 0.13%
appstate san

san-store's Introduction

san-store

NPM version License Build Status

San 框架的官方应用状态管理套件,其理念是类似 flux 的单向流。

虽然应用状态管理已经是流行的理念,我们依然推荐您在开始之前,先看看 为什么要进行应用状态管理,可能会有些思考和启发。

flow

提示:使用 san-store 需要同时使用 san-update 2.x 创建状态变更器,san-store 将使用此变更器更新 store 中的应用状态。

下载

NPM:

$ npm i --save san-store san-update

使用

import {store, connect} from 'san-store';
import {builder} from 'san-update';


store.addAction('changeUserName', function (name) {
    return builder().set('user.name', name);
});


let UserNameEditor = connect({
    name: 'user.name'
})(san.defineComponent({
    submit() {
        store.dispatch('changeUserName', this.data.get('name'));
    }
}));

从例子开始和模仿比死啃枯燥的文档要更人性化。 Todos项目RealWorld App 展示了如何在项目里使用 san-store 进行状态管理。 多store的Todos项目 展示了如何再项目中使用 多 store connect 及 composition api 的用法。

本文档描述了 san-store 的基本使用场景。想了解 san-store 都提供了什么,可以参阅 API文档

单Store与多Store

一个应用具有唯一的应用状态源,在一个地方管理整个应用的所有状态,是一个比较共识的方式。所以 san-store 提供了默认的 Store 实例。绝大多数时候,应用开发者不需要手工创建自己的 Store 实例,只需要 import 默认的 store。

import {store} from 'san-store';

// 通过 getState 方法,获取 store 中的状态数据。
console.log(store.getState('user.name'));

store 并没有提供修改状态数据的方法,修改状态数据只能通过 dispatch action 来做到,具体细节请参考 Action 章节。通过 addAction 方法可以添加 action。

store.addAction('changeUserName', name => builder().set('user.name', name));

当同一系统中有不同团队开发自己的业务模块,各团队之间没有状态共享,可以考虑分别建立 Store 实例进行开发。通过 new Store 创建自己的 Store 实例。创建时可以传入初始化数据和声明 actions。

import {Store} from 'san-store';
import {builder} from 'san-update';


let myStore = new Store({
    initData: {
        user: {
            name: 'your name'
        }
    },

    actions: {
        changeUserName(name) {
            return builder().set('user.name', name);
        }
    }
});

本节最后,还是要强调下,应用开发应当遵循 一个应用具有唯一的应用状态源。说白了就是 要按常理出牌

Action

通过 addAction 方法可以为 Store 添加 Action。 Action 是 san-store 最重要的组成部分之一,它:

  1. 是一个函数
  2. 在一个 store 内每个 action 具有唯一名称,通过名称 dispatch
  3. store 更新状态的唯一入口
  4. 状态更新是同步的,这使得状态更新可依赖当前状态环境,可被记录、被追溯和重放

如果你使用了 san-store,Action 应该是你业务组件的唯一出口:用户操作事件等需要改变应用状态时,都应该 dispatch Action。

变更应用状态

Action 接收一个 payload,返回一个 san-update 的 builder 对象。store 使用 builder 对象生成状态变更函数,并执行它,使 store 内部的状态得到更新。当然,如果当前 action 不期望对 store 的状态进行更新,可以不返回 builder 对象。

import {builder} from 'san-update';

store.addAction('changeUserName', function (name) {
    return builder().set('user.name', name);
});

// 通过名称 dispatch
store.dispatch('changeUserName', 'erik');

san-update 是一个 Immutable 的更新对象库,其提供了一些更新函数(如set、push等),通过 newObj = set(oldObj, 'x', 1) 的使用形式让对象更新 Immutable。builder 是 san-update 提供的一个很好用的功能,通过 builder 你可以预定义一系列的数据更新操作,然后通过 builder.build 方法可以获得一个更新函数。san-store 就是利用这个功能,使用 action 返回的 builder 生成对象更新函数,再调用它进行 store 内部状态更新。

san-update 的 builder 支持预定义所有 san-update 支持的数据操作,通过 san-update文档:可用指令 可以查看所有操作类型。常用的有:

  • apply: 对现有数据项应用更新
  • set: 设置数据项
  • remove: 数组移除项
  • push: 数组push操作
  • pop: 数组pop操作
  • unshift: 数组unshift操作
  • shift: 数组shift操作
  • splice: 数组splice操作

使用前请阅读 san-update文档:使用builder构建更新函数 进行详细了解。

获取当前应用状态

Action 的第二个参数是一个对象,其中的 getState 方法可以用于获得当前 store 中的应用状态。这个方法是 this 无关的。

import {builder} from 'san-update';

store.addAction('initCount', function (count, {getState}) {
    if (getState('count') == null) {
        return builder().set('count', count);
    }
});

store.dispatch('initCount', 10);

如果我们的更新操作仅依赖于当前数据状态项的值,也可以使用 san-update 提供的 apply 方法。

import {builder} from 'san-update';

store.addAction('initCount', function (count) {
    // apply 意思是:在原有的值上应用新的值
    return builder().apply('count', oldValue => {
        return oldValue == null ? count : oldValue;
    });
});

store.dispatch('initCount', 10);

异步过程

同步的 Action 返回一个 builder,并立即更新数据状态,但我们经常会遇到异步的场景,常见的比如请求数据、返回并更新应用状态。Action 在设计上作为 业务组件的唯一出口, 对异步支持的方式如下:

  1. 返回一个 Promise 时,当前 Action 为异步
  2. 返回一个 builder 或什么都不返回时,当前 Action 为同步

下面是一个简单的例子: 一个列表请求的行为,此时要显示 loading,在请求返回时更新应用状态中的列表项,同时隐藏 loading。

import {builder} from 'san-update';

store.addAction('fetchList', function (page, {getState, dispatch}) {
    dispatch('showLoading');
    dispatch('updateCurrentPage', page);

    return requestList(page).then(list => {
        if (getState('currentPage') === page) {
            dispatch('updateList', list);
            dispatch('hideLoading');
        }
    });
});

store.addAction('showLoading', function () {
    return builder().set('loading', true);
});

store.addAction('hideLoading', function () {
    return builder().set('loading', false);
});

store.addAction('updateCurrentPage', function (page) {
    return builder().set('currentPage', page);
});

store.addAction('updateList', function (list) {
    return builder().set('list', list);
});


// 这里模拟一下,意思意思
function requestList(page) {
    return new Promise(resolve => {
        setTimeout(() => {
            let pageList = [1, 2, 3];
            resolve(pageList);
        }, 500);
    });
}

例子中有下面几个要点:

  1. 异步 Action 可以多次 dispatch 其他的 Action,通过第二个参数对象中的 dispatch 方法。这个方法和 getState 一样,也是 this 无关的。
  2. fetchList 中马上 updateCurrentPage,在请求返回时使用 getState 方法对 currentPage 判断,能够避免用户快速多次点击页码时发起多个 list 请求,请求返回的顺序不同可能导致问题。
  3. 异步 Action 没有更新应用状态的能力,想要更新应用状态必须 dispatch 同步 Action。下面的代码说明了为什么,感兴趣可以看看。
store.addAction('fetchList', function (page, {getState, dispatch}) {
    dispatch('showLoading');
    dispatch('updateCurrentPage', page);

    return requestList(page).then(list => {
        if (getState('currentPage') === page) {
            dispatch('hideLoading');
            
            // 如果异步 Action 支持在 promise 中返回 builder 并更新状态
            // 这里的代码就可能导致问题。因为 promise.then 不是马上运行的
            // 这里的 currentPage 不代表 builder 运行时的 currentPage
            // currentPage 可能被另外一个 dispatch fetchList 改掉
            // 所以这里应该 dispatch 一个同步的 Action 让应用状态即时完成变更
            dispatch('updateList', list);  // good
            // return builder().set('list', list); // warning
        }
    });
});

异步 Action 在 dispatch 时将返回 Promise 对象,以便于 Action 完成后的逻辑控制。

store.addAction('addArticle', function (article) {
    return axios.post(url, article);
});


store.dispatch('addArticle', {}).then(() => {
    // redirect to view page
});

组件的connect

connect到默认store

connect 方法支持对 默认store实例San 组件进行连接,步骤和 redux 类似:

  1. 通过 connect 方法创建一个 connect 组件的函数
  2. 调用这个函数对组件进行 connect
import {store, connect} from 'san-store';

const connector = connect(
    {name: 'user.name'},
    {change: 'changeUserName'}
);
const UserNameEditor = san.defineComponent({/*...*/});
const NewUserNameEditor = connector(UserNameEditor);

通常,我们只需要对当前声明的组件进行 connect。此时可以合并成一句

const UserNameEditor = san.defineComponent({/*...*/});
const NewUserNameEditor = connect(
    {name: 'user.name'},
    {change: 'changeUserName'}
)(UserNameEditor);

connect到自己创建的store

当实际业务中需要多个 Store 实例时,可以自行创建 Store 实例。connect 方法支持连接到指定的 Store 实例。

import {Store, connect} from 'san-store';

// 自行创建 store 实例
const myStore = new Store({
    initData: {
        name:'erik'
    },
    actions:{
        changeUserName(name) {
            return builder().set('user.name', name);
        }
    }
});

const UserNameEditor = san.defineComponent({/*...*/});

// connect 时提供自创建的 storeA 
const NewUserNameEditor = connect(
    myStore,
    {name: 'user.name'},
    {change: 'changeUserName'}
)(UserNameEditor);

映射store状态与组件data

mapStates 参数指定了要把哪些状态注入到组件,key 是要注入到组件的数据项名称,value 是 store 中状态项的名称。

import {store, connect} from 'san-store';

const NewUserNameEditor = connect(
    {name: 'user.name'}
)(san.defineComponent({
    // connect 后,name 数据项由 store 提供
    template: '<div title="{{name}}">......</div>'
}));

组件上可直接调用的 dispatch action 方法

使用 store 时,我们可以通过调用 store.dispatch(actionName, payload) 方法更新应用状态。但如果在组件中也这么做,就会有些问题:

  • 由于 actionName 的应用全局唯一性,名字需要比较完整,对于组件来说这么长的名称会显得比较冗余
  • 组件实现时,得关心对具体 store 的依赖

通过指定 mapActions 可以在组件的 actions 成员上生成 dispatch action 的快捷方法,让组件可以更便捷的 dispatch action。mapActions 的 key 是要映射到组件 actions 成员上的方法名,value 是 action 的名称。

import {store, connect} from 'san-store';

const UserNameEditor = connect(
    {name: 'user.name'},
    {change: 'changeUserName'}
)(san.defineComponent({
    submit() {
        // 通过 mapActions,可以把 dispatch action 简化成组件自身的方法调用
        // store.dispatch('changeUserName', this.data.get('name'));
        this.actions.change(this.data.get('name'));
    }
}));

connect 多个 store

当实际业务中真的需要多个 Store 实例时,可以通过 connect 自行创建 connector,连接 Store 实例和 San 组件。步骤如下:

  1. 创建 Store 实例
  2. 通过 connect().connect() 方式,即 connect 的链式调用实现 连接多个 store
import {Store, connect} from 'san-store';

// 创建模块A中的store实例
const storeA = new Store({
    initData: {
        name:'erik'
    },
    actions:{
        changeUserName(name) {
            return builder().set('user.name', name);
        }
    }
});

const storeB = new Store({
    initData: {
        todos: []
    },
    actions:{
        changeTodos(payload) {
            return builder().set('todos', payload);
        }
    }
});

const Container = san.defineComponent({/*...*/});

// 调用手动创建的connectA方法进行storeA和组件连接
const NewContainer = connect(
    storeA,
    {name: 'user.name'},
    {change: 'changeUserName'}
).connect(
    storeB,
    {todos: 'todos'},
    ['changeTodos']
)(Container);

componsition api

use api 提供了对 san-composition 形式组件的 store 支持:

  1. 使用 useState 定义数据。
  2. 使用 useAction 定义方法。
import san from 'san';
import {defineComponent, template, method} from 'san-composition';
import {useState} from 'san-store/use';
import {Store} from 'san-store';

// 创建模块A中的store实例
const myStore = new Store({
    initData: {
        name:'erik'
    },
    actions:{
        changeUserName(name) {
            return builder().set('user.name', name);
        }
    }
});

export default defineComponent(context => {
    template(`
        <div>{{name}}
            <input value="{=newName=}"><button on-click="change">change</button>
        </div>
    `);

    const name = useState(myStore, 'user.name', 'name');
    const newName = data('newName', '');
    let changeUserName = useAction(myStore, 'changeUserName');

    method({
        change: () => {
            changeUserName(newName.get());
        }
    });

}, san);

san-store's People

Contributors

buptlhuanyu avatar dafrok avatar dependabot[bot] avatar erik168 avatar errorrik avatar jiangjiu avatar jinzhan avatar luoyimaid avatar luyuan avatar marxjiao avatar sharonzd avatar x-jray avatar zttonly avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

san-store's Issues

连续操作同一数据字段无效问题

如果运行下面的一段DEMO代码,发现虽然调用了pos这个action对所有数据的序号进行更新,但并未全部生效。

想到 san-update 中有对 chain 做了这样描述:链式调用不保证顺序,操作同一数据会带来无法预知的后果。猜测,是否不同action对同一数据字段的修改最后都归一成了一次链式调用?

那么,有这样的需求,如何编写逻辑才是最佳呢?

var {
    store,
    connect
} = window['san-store'];
var {
    updateBuilder
} = window['sanUpdate']

store.addAction('init', function (item) {
    return updateBuilder().set('core', {
        array: []
    });
});

store.addAction('push', function (items,{getState}) {
    var array = getState('core.array');
    return updateBuilder().splice('core.array', array.length, items.length, ...items);
});

store.addAction('pos', function (item, {
    getState
}) {
    var array = getState('core.array');
    var ub = updateBuilder();

    for (let i = 0; i < array.length; ++i) {
        ub = ub.set(`core.array[${i}].pos`, 1 + i);
    }

    return ub;
});

store.dispatch('init');

var App = san.defineComponent({
    template: `
        <div>
            <div on-click="onClick">Click</div>
            <ul>
                <li s-for="item in array">{{item.pos}}-{{item.key}}</li>
            </ul>
        </div>`,
    onClick: function () {
        this.actions.push([{
            key: Math.random(),
            pos: 1
        }, {
            key: Math.random(),
            pos: 2
        }, {
            key: Math.random(),
            pos: 3
        }]);
        this.actions.pos();
    }
});

const My = connect.san({
    array: 'core.array'
}, {
    push: 'push',
    pos: 'pos'
})(App);


new My().attach(document.querySelector('#root'));

强调connect型的组件和视图型组件的分离

从一个应用系统的角度来看,将“与store进行连接”和“视图展现”混搭起来似乎并不是一个好的实践,store承载着应用状态必然与业务关联,而业务与视图分离在任何架构中都是一个准则,因此像这样的代码还是避免出现:

<template>
<div>
    <select value="{{year}}>
        <option>...</option>
    </select>
</div>
</template>
<script>
@connect(...)
class Calendar extends Component {

}
</script>

这是一个将日历这样的视图组件和业务上与store连接混合在一起的,在导致业务逻辑(各种mapping)和视图混杂的情况下,同时也让视图部分无法被复用,因此一种更好的方式是:

import {Calendar} from '../Calendar/index';
import {connect} from 'san-store';

export default connect(mapToData)(Calendar)

这样子将视图部分(日历)和业务部分(connect)分离干净

当然我们可以考虑提供一些通用的connect相关的组件,比如是否能这样写:

<s-connector>
    <slot name="mapToData">
        <s-map from="..." to="..." />
        <s-map from="..." to="..." />
        <s-map from="..." to="..." />
    </slot>
    <s-calendar ... />
</s-connector>

同时修改数组中的多个值,只会有一个生效

const { builder } = require('san-update')
const san = require('san')
const { connect, store } = require('san-store')

const MyComponent = connect.san(
    { arr: 'arr' }
)(san.defineComponent({
    template: ''
}))

store.raw =  {
    arr: [1,2,3,4,5]
}
store.addAction('change', () => {
    return builder().set('arr.0', 'a').set('arr.1', 'b')
})

const app = new MyComponent()
console.log(app.data.raw) // { arr: [ 1, 2, 3, 4, 5 ] }
store.dispatch('change')
console.log(app.data.raw) // { arr: [ 'a', 2, 3, 4, 5 ] } 应当为 { arr: [ 'a', 'b', 3, 4, 5 ] }

这里应该需要遍历所有 diff:

function calcUpdateInfo(info, diff) {

移除store上的dispatch和fire等方法

API设计如下:

import {createStore} from 'san-store';

let {store, dispatch, subscribe} = createStore(...);

redux的dispatchsubscribe是放在store上的,给人一种需要有this的感觉。我觉得直接以函数的形式返回会更好一些,内部实现上也不用依赖mini-event

组件采用 class 写法时会报错

分析了一下 san-store 的代码,主要问题应该出在 src/connect/createConnect.js 的 connect 方法 L59,在实现类继承的时候存在两个问题:

  1. 比如 template、components 等静态属性无法被派生类继承;
  2. ComponentClass 通过 call 方法执行会直接报错;

感觉这块代码可以直接上 ES6 class 实现:

if (typeof ComponentClass === 'function') {
    componentProto = ComponentClass.prototype;
    ReturnTarget = class extends ComponentClass {};

    // let F = new Function();
    // F.prototype = componentProto;

    // ReturnTarget = function (option) {
    //     ComponentClass.call(this, option);
    // };

    // ReturnTarget.prototype = new F();
    // ReturnTarget.prototype.constructor = ReturnTarget;
    extProto = ReturnTarget.prototype;
}

TODO Demo 特别卡

Hi, @erik168 , 我把 ToDo List Demo clone 到我本地运行了一下,发现在现代浏览器(e.g [email protected])上特别卡,尤其在切换分类标签的时候。一脸懵逼不知道为什吗?

另外,在 IE7 上运行,报语法错误。

异步

  1. 现在的action是完全同步的,update state由action负责
  2. 希望san-store为应用开发提供一个无论同步或异步过程的统一出口

所以要有异步模型。他山之石可以攻玉,看看别人都咋玩的

vuex

vuex是action是异步的,mutation是同步的,mutation等于我们的action,说白了就是两个抽象,一个同步一个异步

redux

dispatch方法接收一个action对象,或者一个函数,当它是函数时,函数接收disaptch和getState参数。

也就是说,dispatch方法接收action对象时,过程是同步的,走进reducers,然后update state。如果是函数,则时机就函数自己看着办了,函数里面可以来个promise,在未来某个时候再调用dispatch方法。

function innerFetchTodos(category) {
    return dispatch => {
        dispatch(requestTodos(category));
        return service.todos(category)
            .then(todos => dispatch(receiveTodos(category, todos)))
    };
}

function requestTodos(category) {
    return {
        type: ActionType.REQUEST_TODOS,
        payload: {
            category
        }
    };
}

function receiveTodos(category, todos) {
    return {
        type: ActionType.RECEIVE_TODOS,
        payload: {
            category,
            todos
        }
    };
}

export function fetchTodos(category) {
    return (dispatch, getState) => {
        let state = getState();

        if (!state.todos.isFetching) {
            return dispatch(innerFetchTodos(category));
        }
    };
}

reflux

reflux 也一样,就是你在action中看着办。整个过程比redux要简单很多,action可以有阶段性事件,更新store要自己找地方存也是槽点(但我们不关心这个)。

const actions = Reflux.createActions({
    list: {
        children: ['completed', 'failed']
    }
});

actions.list.listen(function (category) {
    service.todos(category)
        .then(this.completed)
        .catch(this.failed);
});
let store = Reflux.createStore({
    listenables: actions,

    onListCompleted(data) {
        todos = data;
        this.trigger(todos);
    },

    getData() {
        return todos;
    }
});

我觉得

我觉得,其实我们并不需要一个新的抽象,多个抽象多些理解成本,而且action的名字很天然挺好的,不管动作是要马上更新状态还是要去后端取数据,其实都是action。我们确实要保证对状态数据更新的同步性,promise保证不了,但我们可以在promise.then里面dispatch action,这时的操作一定是同步的。

addAction('fetchList', page => {
    requestList(page).then(list => {
        this.dispatch('updateList', list);
    });

    return builder.set('list', null);
    // 或者可以this.dispatch('resetList')。我现在觉得,action里dispatch action也不是接受不了的事情
});

一例模型视图没有同步的 case

环境

复现用例

https://codepen.io/Dafrok/pen/zwMgzp

逻辑描述

复现场景较为复杂。

store 属性

  1. root:该属性负责存放节点树,每个 {name<String>, children<Array>} 结构的对象视为一个节点,children 存放该节点的子节点。
  2. path:该节点负责存放被激活的节点路径。暂称为 激活路径。如 store.getState('root.children[1]') 被激活时,path 属性的值为 'root.children[1]'`。

组件

  1. 根组件:该组件负责 connect store和处理业务逻辑。组件含有一个按钮,点击按钮会向 store 的 激活路径 push 一个节点对象。
  2. 树形结构组件:根据 store 的 root 属性输出一个节点树。点击某个节点可以改变 激活路径 为这个节点的路径。
  3. 列表组件:仅展示 激活路径 的所属节点的子节点。

期望输出

点击 树形结构组件 激活一个节点,然后点击『插入节点』按钮后,树形结构组件 被激活的节点下插入一个节点,同时 列表组件 也插入一个节点。

实际输出

点击 树形结构组件 激活一个节点,然后点击『插入节点』按钮后,树形结构组件 被激活的节点下插入一个节点,而 列表组件 没有更新。再次激活该节点时,被插入的节点才更新到视图上。

简化get方法

从Store的层面上来说,只需要getData()方法返回整个数据就行,一个支持path的get方法出现在connect相关的模块里更合适

不要让Action可以继续dispatch

理由有几点:

  1. 我们先做最小功能的集合,这个需求后续真的存在的话再加不迟,现在就加上只会导致去不掉
  2. 本身Action中继续dispatch是可能引起死循环的,redux中在subscribe章节特别说了这个问题
  3. Action本身是同步的,这意味着他应该能一次性完成所有的工作,那么同步再通过dispatch触发另一个同步似乎并没有意义,Action不应该是作为“数据修改的逻辑”的复用单元
  4. 避免因为dispatch的存在让人在Action中进行异步操作之后再进行dispatch

建议connect返回派生类

从源码来看,connect是没有使用派生类的,这样应该很有危险吧?

如:有基础组件 BasicComponent这个组件可能在多处(可能在同一个视图中)被使用,原则上不应该有人直接去connent()(BasicComponent),但是一旦有人这样做了,这个BasicComponent 就不是原来的BasicComponent 了,这是个很严重的隐患。

求教关于单例store与侵入型connect的最佳实践

首先,虽然store是可以自行创建的,但connect却只支持全局的这个单例store。

其次,connect.san()(MyComponent) === MyComponent,这是否意味着MyComponent这样一个class定义,就只能为全局一个store服务?

在多频道(Tab)这样的 共享内存 但业务数据却 严格分离 的应用中,如何设计可使san-store的应用最合理高效?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.