Giter Club home page Giter Club logo

blog's People

Watchers

 avatar

blog's Issues

面试合集

写是写了 但是不想发出来了。哈哈

Webpack 九浅一深系列(一)—— 概念梳理

Webpack 九浅一深系列(一)—— 概念梳理

前言 TL,DR

笔者虽入行前端已有时日,但从纯小白走过来的日子仍历历在目。随着工作经历的不断增加,愈发感到前端工程师的技能树真是枝杈略多,难成系统主干。过去靠小聪明和运气博得些 offer,可自己真的算是一个合格的前端工程师了吗?

从学习本身的客观规律上讲,对一块知识形成系统化认知才算是「学会」。审视自身所学,不免惶惶。因此,本系列就是个踏踏实实的整体梳理与部分深入,旨在扎扎实实将一个前端工程师应有的技能「熟悉」(好孩子不要简历中写「掌握」哟)。于笔者而言,愿起到温故知新、查缺补漏之效;于读者而言,也愿各取所需,有所收获。

古语有云:开局有高度,学习不糊涂。本篇旨在 Webpack 学习过程的开始阶段,从宏观角度构建起 Webpack 的知识结构。后续学习到的每一块知识,每一点细节,都能在结构上有所对应,这样也就能取得较好的效果。

正文开始。

What & Why

Webpack 是什么呢?官方文档解释为静态模块打包器,当然这个解释更像是早起的 Webpack,如今其功能已经更为广泛而强大。当然,文档的意思是说,Webpack 就是将我们的前端资源(代码、样式、依赖的三方包、图片、字体等等)根据模块之间的依赖关系,按照我们想要的方式整合成一个或多个 bundle。打包的过程中,我们可能会做一些编译,可能会将代码拆分成若干 chunk,可能会压缩代码,可能会针对生产环境的进行优化。总的来说,Webpack 是目前最为流行的打包工具,可以很好的满足我们构建应用的需要。

虽然从零到一搭建一个 Webpack + xxx + yyy 的文章很多,但自己搞一遍其实还是需要了解多方面的知识的。不过没关系,著名的 create-react-app 可以迅速搭建起一个项目,其内部就是借助了 Webpack 的能力,通过 npm run eject 命令就可以看到其实际的配置。

如果还不是很清晰,那或许你需要了解下什么是模块规范,过去到现在 JavaScript 文件的组织方式经历了怎样的变化。不过这些并不是本文的重点,我们可以在许多文章中看到类似的发展历史。笔者自己入行仍短,许多 AMD、CMD 的东西也只是很早的时候用过一用,就不多说了。

React Hooks 梳理

自 React 16.8 发布以后,在已有项目中,把 package.json 中的 react 和 react-dom 版本一升,就可以抄起 Hooks 开干了。笔者目前已经在项目中开始了实操,但不妨先总结下官方文档中一些值得梳理的点。

useState

为什么 useState 不叫 createState 呢?

  • 初始渲染时,useState 返回的是 initState
  • 下次渲染时,useState 返回的是 curState

也就是说,create 的叫法就不太符合初始渲染之后获取到的是「当前状态」这么一个事实了。

为什么 useState 不通过 this 也知道自己是哪个 Component 的状态?

每个组件内部都有一个「内存格子」的列表,他们就是一些存放数据的 JS 对象,当我们使用如 useState 的 Hooks 时,就会去读取当前的格子(或者在初始渲染的时候进行初始化),然后将指针移动到下一个 Hooks。这就是为什么一个组件内部的多个 useState 都能获取到各自的局部状态。

但是需要注意的是,这也是为什么官方建议我们要将 hooks 的调用顺序保持一致

useEffect

和过去的生命周期有什么区别?

其一,React 会在每次渲染完成后会调用 useEffect,如果使用传统的生命周期钩子的话,当我们希望每次 render 后执行某种副作用时,我们不得不在 componentDidMount 和 componentDidUpdate 里都塞上相同的逻辑,带来冗余。因此,传统的生命周期是不能代替 useEffect 的。这一点可参考 React Class 生命周期

当然,相比较考虑 mount 和 update,只考虑 render 是要简单清晰不少。

其二,Hooks 让我们可以基于逻辑而拆分代码,而不是基于生命周期。这一点非常重要,因为基于生命周期来拆分代码,势必让逻辑相关联的代码分散各处。使用 Hooks,我们就可以按照我们指定的顺序使用每一个副作用。

传入的函数每次 render 都是新的?

是的,这是为了保证在 useEffect 中使用到的内部状态都是最新的。这样 useEffect 就很像是 render 的一部分了 —— 每次使用的 useEffect 都属于其对应的的 render。

不仅如此,我们在 useEffect 中 return 的方法,也即通常用来做取消订阅这类 cleanup 工作的,每次 render 后也都会执行一次新的副作用(准确的说会先走 return 的方法,再重新走一次 useEffect 中的方法),而绝不是 unmount 的时候才执行一次。这种模式会有更少的 bug。

什么样的 bug 呢?可以看官方文档的例子,大致就是说,如果我们订阅的人的 id 变了,就需要取消订阅然后重新订阅新的人。这样一来,如果在使用 class 做订阅这类处理时,就需要在 3 个生命周期(componentDidMount、componentDidUpdate、componentWillUnmount)里散布逻辑,即在 componentDidUpdate 补充上取消并重新订阅的逻辑!

如果用了 useEffect,这些东西根本不需要去考虑。整个过程如文档中给的例子一样依次执行:

function FriendStatus(props) {
  // ...
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect

useEffect 第二个参数的优化作用

对 return 的 cleanup 同样适用,不要忘了,每次 render 完就会先执行一次 cleanup,最终 unmount 的时候也会执行一次 cleanup。

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 只会在 props.friend.id 变化的时候重新订阅

如果我们不提供该参数,每次更新都会重新执行;如果只想 mount 和 unmount 的时候各执行一次,可指定 [],但这不是好的实践方式,考虑到 useEffect 都是在 render 完后执行的,多做点工作可能会少点问题。

Hooks 使用原则

Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.

这一条的原因是,Hooks 是通过调用顺序分配存放位置的,只有每次 run 的时候顺序保持一致,才能挨个取得正确的 useState、useEffect。比方说,如果我们把 Hooks 放到条件语句里,然后第一次 render 的时候每个都执行,第二次 render 却有一个 Hook 不执行,那么后面的对应就出错了。很好理解吧。

但如果我们一定要有条件的执行 useEffect 呢?我们可以在 useEffect 内部加条件

  useEffect(function persistForm() {
    // 👍 这样就不会破坏第一条原则
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

Only Call Hooks from React Functions.

这条没什么说的,总之只在下面两处用 Hooks:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks.

React 性能优化技巧总结

本文将从 render 函数的角度总结 React App 的优化技巧。需要提醒的是,文中将涉及 React 16.8.2 版本的内容(也即 Hooks),因此请至少了解 useState 以保证食用效果。

正文开始。


当我们讨论 React App 的性能问题时,组件的渲染速度是一个重要问题。在进入到具体优化建议之前,我们先要理解以下 3 点:

  1. 当我们在说「render」时,我们在说什么?
  2. 什么时候会执行「render」?
  3. 在「render」过程中会发生什么?

解读 render 函数

这部分涉及 reconciliation 和 diffing 的概念,当然官方文档在这里

当我们在说「render」时,我们在说什么?

这个问题其实写过 React 的人都会知道,这里再简单说下:

在 class 组件中,我们指的是 render 方法:

class Foo extends React.Component {
 render() {
   return <h1> Foo </h1>;
 }
}

在函数式组件中,我们指的是函数组件本身:

function Foo() {
  return <h1> Foo </h1>;
}

什么时候会执行「render」?

render 函数会在两种场景下被调用:

1. 状态更新时

a. 继承自 React.Component 的 class 组件更新状态时
import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  render() {
    return <Foo />;
  }
}

class Foo extends React.Component {
  state = { count: 0 };

  increment = () => {
    const { count } = this.state;

    const newCount = count < 10 ? count + 1 : count;

    this.setState({ count: newCount });
  };

  render() {
    const { count } = this.state;
    console.log("Foo render");

    return (
      <div>
        <h1> {count} </h1>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

可以看到,代码中的逻辑是我们点击就会更新 count,到 10 以后,就会维持在 10。增加一个 console.log,这样我们就可以知道 render 是否被调用了。从执行结果可以知道,即使 count 到了 10 以上,render 仍然会被调用。

总结:继承了 React.Component 的 class 组件,即使状态没变化,只要调用了setState 就会触发 render。

b. 函数式组件更新状态时

我们用函数实现相同的组件,当然因为要有状态,我们用上了 useState hook:

import React, { useState } from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  render() {
    return <Foo />;
  }
}

function Foo() {
  const [count, setCount] = useState(0);

  function increment() {
    const newCount = count < 10 ? count + 1 : count;
    setCount(newCount);
  }

  console.log("Foo render");
  
  return (
    <div>
      <h1> {count} </h1>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

我们可以注意到,当状态值不再改变之后,render 的调用就停止了。

总结:对函数式组件来说,状态值改变时才会触发 render 函数的调用。

2. 父容器重新渲染时

import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  state = { name: "App" };
  render() {
    return (
      <div className="App">
        <Foo />
        <button onClick={() => this.setState({ name: "App" })}>
          Change name
        </button>
      </div>
    );
  }
}

function Foo() {
  console.log("Foo render");

  return (
    <div>
      <h1> Foo </h1>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

只要点击了 App 组件内的 Change name 按钮,就会重新 render。而且可以注意到,不管 Foo 具体实现是什么,Foo 都会被重新渲染。

总结:无论组件是继承自 React.Component 的 class 组件还是函数式组件,一旦父容器重新 render,组件的 render 都会再次被调用。

在「render」过程中会发生什么?

只要 render 函数被调用,就会有两个步骤按顺序执行。这两个步骤非常重要,理解了它们才好知道如何去优化 React App。

Diffing

在此步骤中,React 将新调用的 render 函数返回的树与旧版本的树进行比较,这一步是 React 决定如何更新 DOM 的必要步骤。虽然 React 使用高度优化的算法执行此步骤,但仍然有一定的性能开销。

Reconciliation

基于 diffing 的结果,React 更新 DOM 树。这一步因为需要卸载和挂载 DOM 节点同样存在许多性能开销。

开始我们的 Tips

Tip #1:谨慎分配 state 以避免不必要的 render 调用

我们以下面为例,其中 App 会渲染两个组件:

  • CounterLabel,接收 count 值和一个 inc 父组件 App 中状态 count 的方法。
  • List,接收 item 的列表。
import React, { useState } from "react";
import ReactDOM from "react-dom";

const ITEMS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

function App() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState(ITEMS);
  return (
    <div className="App">
      <CounterLabel count={count} increment={() => setCount(count + 1)} />
      <List items={items} />
    </div>
  );
}

function CounterLabel({ count, increment }) {
  return (
    <>
      <h1>{count} </h1>
      <button onClick={increment}> Increment </button>
    </>
  );
}

function List({ items }) {
  console.log("List render");

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item} </li>
      ))}
    </ul>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

执行上面代码可知,只要父组件 App 中的状态被更新,CounterLabelList 就都会更新。

当然,CounterLabel 重新渲染是正常的,因为 count 发生了变化,自然要重新渲染;但是对于 List 而言,就完全是不必要的更新了,因为它的渲染与 count 无关。尽管 React 并不会在 reconciliation 阶段真的更新 DOM,毕竟完全没变化,但是仍然会执行 diffing 阶段来对前后的树进行对比,这仍然存在性能开销。

还记得 render 执行过程中的 diffing 和 reconciliation 阶段吗?前面讲过的东西在这里碰到了。

因此,为了避免不必要的 diffing 开销,我们应当考虑将特定的状态值放到更低的层级或组件中(与 React 中所说的「提升」概念刚好相反)。在这个例子中,我们可以通过将 count 放到 CounterLabel 组件中管理来解决这个问题。

Tip #2:合并状态更新

因为每次状态更新都会触发新的 render 调用,那么更少的状态更新也就可以更少的调用 render 了。

我们知道,React class 组件有 componentDidUpdate(prevProps, prevState) 的钩子,可以用来检测 props 或 state 有没有发生变化。尽管有时有必要在 props 发生变化时再触发 state 更新,但我们总可以避免在一次 state 变化后再进行一次 state 更新这种操作:

import React from "react";
import ReactDOM from "react-dom";

function getRange(limit) {
  let range = [];

  for (let i = 0; i < limit; i++) {
    range.push(i);
  }

  return range;
}

class App extends React.Component {
  state = {
    numbers: getRange(7),
    limit: 7
  };

  handleLimitChange = e => {
    const limit = e.target.value;
    const limitChanged = limit !== this.state.limit;

    if (limitChanged) {
      this.setState({ limit });
    }
  };

  componentDidUpdate(prevProps, prevState) {
    const limitChanged = prevState.limit !== this.state.limit;
    if (limitChanged) {
      this.setState({ numbers: getRange(this.state.limit) });
    }
  }

  render() {
    return (
      <div>
        <input
          onChange={this.handleLimitChange}
          placeholder="limit"
          value={this.state.limit}
        />
        {this.state.numbers.map((number, idx) => (
          <p key={idx}>{number} </p>
        ))}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

这里渲染了一个范围数字序列,即范围为 0 到 limit。只要用户改变了 limit 值,我们就会在 componentDidUpdate 中进行检测,并设定新的数字列表。

毫无疑问,上面的代码是可以满足需求的,但是,我们仍然可以进行优化。

上面的代码中,每次 limit 发生改变,我们都会触发两次状态更新:第一次是为了修改 limit,第二次是为了修改展示的数字列表。这样一来,每次 limit 的变化会带来两次 render 开销:

// 初始状态
{ limit: 7, numbers: [0, 1, 2, 3, 4, 5, 6]
// 更新 limit -> 4
render 1: { limit: 4, numbers: [0, 1, 2, 3, 4, 5, 6] } // 
render 2: { limit: 4, numbers: [0, 2, 3]

我们的代码逻辑带来了下面的问题:

  • 我们触发了比实际需要更多的状态更新;
  • 我们出现了「不连续」的渲染结果,即数字列表与 limit 不匹配。

为了改进,我们应避免在不同的状态更新中改变数字列表。事实上,我们可以在一次状态更新中搞定:

import React from "react";
import ReactDOM from "react-dom";

function getRange(limit) {
  let range = [];

  for (let i = 0; i < limit; i++) {
    range.push(i);
  }

  return range;
}

class App extends React.Component {
  state = {
    numbers: [1, 2, 3, 4, 5, 6],
    limit: 7
  };

  handleLimitChange = e => {
    const limit = e.target.value;
    const limitChanged = limit !== this.state.limit;
    if (limitChanged) {
      this.setState({ limit, numbers: getRange(limit) });
    }
  };

  render() {
    return (
      <div>
        <input
          onChange={this.handleLimitChange}
          placeholder="limit"
          value={this.state.limit}
        />
        {this.state.numbers.map((number, idx) => (
          <p key={idx}>{number} </p>
        ))}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Tip #3:使用 PureComponent 和 React.memo 以避免不必要的 render 调用

我们在之前的例子中看到将特定状态值放到更低的层级来避免不必要渲染的方法,不过这并不总是有用。

我们来看下下面的例子:

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [isFooVisible, setFooVisibility] = useState(false);

  return (
    <div className="App">
      {isFooVisible ? (
        <Foo hideFoo={() => setFooVisibility(false)} />
      ) : (
        <button onClick={() => setFooVisibility(true)}>Show Foo </button>
      )}
      <Bar name="Bar" />
    </div>
  );
}

function Foo({ hideFoo }) {
  return (
    <>
      <h1>Foo</h1>
      <button onClick={hideFoo}>Hide Foo</button>
    </>
  );
}

function Bar({ name }) {
  return <h1>{name}</h1>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

可以看到,只要父组件 App 的状态值 isFooVisible 发生变化,Foo 和 Bar 就都会被重新渲染。

这里因为为了决定 Foo 是否要被渲染出来,我们需要将 isFooVisible 放在 App中维护,因此也就不能将状态拆出放到更低的层级。不过,在 isFooVisible 发生变化时重新渲染 Bar 仍然是不必要的,因为 Bar 并不依赖 isFooVisible。我们只希望 Bar 在传入属性 name 变化时重新渲染。

那我们该怎么搞呢?两种方法。

其一,对 Bar 做记忆化(memoize):

const Bar = React.memo(function Bar({name}) {
  return <h1>{name}</h1>;
});

这就能保证 Bar 只在 name 发生变化时才重新渲染。

此外,另一个方法就是让 Bar 继承 React.PureComponent 而非 React.Component:

class Bar extends React.PureComponent {
 render() {
   return <h1>{name}</h1>;
 }
}

是不是很熟悉?我们经常提到使用 React.PureComponent 能带来一定的性能提升,避免不必要的 render。

总结:避免组件不必要的渲染的方法有:React.memo 包裹的函数式组件,继承自 React.PureComponent 的 class 组件

为什么不让每个组件都继承 PureComponent 或者用 memo 包呢?

如果这条建议可以让我们避免不必要的重新渲染,那我们为什么不把每个 class 组件变成 PureComponent、把每个函数式组件用 React.memo 包起来?为什么有了更好的方法还要保留 React.Component 呢?为什么函数式组件不默认记忆化呢?

毫无疑问,这些方法并不总是万灵药。

嵌套对象的问题

我们先来考虑下 PureComponent 和 React.memo 的组件到底做了什么?

每次更新的时候(包括状态更新或上层组件重新渲染),它们就会在新 props、state 和旧 props、state 之间对 key 和 value 进行浅比较。浅比较是个严格相等的检查,如果检测到差异,render 就会执行:

// 基本类型的比较
shallowCompare({ name: 'bar'}, { name: 'bar'}); // output: true
shallowCompare({ name: 'bar'}, { name: 'bar1'}); // output: false

尽管基本类型(如字符串、数字、布尔)的比较可以工作的很好,但对象这类复杂的情况可能就会带来意想不到的行为:

shallowCompare({ name: {first: 'John', last: 'Schilling'}},
			   { name: {first: 'John', last: 'Schilling'}}); // output: false

上述两个 name 对应的对象的引用是不同的。

我们重新看下之前的例子,然后修改我们传入 Bar 的 props:

import React, { useState } from "react";
import ReactDOM from "react-dom";

const Bar = React.memo(function Bar({ name: { first, last } }) {
  console.log("Bar render");

  return (
    <h1>
      {first} {last}
    </h1>
  );
});

function Foo({ hideFoo }) {
  return (
    <>
      <h1>Foo</h1>
      <button onClick={hideFoo}>Hide Foo</button>
    </>
  );
}

function App() {
  const [isFooVisible, setFooVisibility] = useState(false);

  return (
    <div className="App">
      {isFooVisible ? (
        <Foo hideFoo={() => setFooVisibility(false)} />
      ) : (
        <button onClick={() => setFooVisibility(true)}>Show Foo</button>
      )}
      <Bar name={{ first: "John", last: "Schilling" }} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

尽管 Bar 做了记忆化且 props 值并没有发生变动,每次父组件重新渲染时它仍然会重新渲染。这是因为尽管每次比较的两个对象拥有相同的值,引用并不同。

函数 props 的问题

我们也可以把函数作为 props 向组件传递,当然,在 JavaScript 中函数也会传递引用,因此浅比较也是基于其传递的引用。

因此,如果我们传递的是箭头函数(匿名函数),组件仍然会在父组件重新渲染时重新渲染

Tip #4:更好的 props 写法

前面的问题的一种解决方法是改写我们的 props。

我们不传递对象作为 props,而是将对象拆分成基本类型

<Bar firstName="John" lastName="Schilling" />

而对于传递箭头函数的场景,我们可以代以只唯一声明过一次的函数,从而总可以拿到相同的引用,如下所示:

class App extends React.Component{
  constructor(props) {
    this.doSomethingMethod = this.doSomethingMethod.bind(this);    
  }
  doSomethingMethod () { // do something}
  
  render() {
    return <Bar onSomething={this.doSomethingMethod} />
  }
}

Tip #5:控制更新

还是那句话,任何方法总有其适用范围。

第三条建议虽然处理了不必要的更新问题,但我们也不总能使用它。

而第四条,在某些情况下我们并不能拆分对象,如果我们传递了某种嵌套确实复杂的数据结构,那我们也很难将其拆分开来。

不仅如此,我们也不总能传递只声明了一次的函数。比如在我们的例子中,如果 App 是个函数式组件,恐怕就不能做到这一点了(在 class 组件中,我们可以用 bind 或者类内箭头函数来保证 this 的指向及唯一声明,而在函数式组件中则可能会有些问题)。

幸运的是,无论是 class 组件还是函数式组件,我们都有办法控制浅比较的逻辑

在 class 组件中,我们可以使用生命周期钩子 shouldComponentUpdate(prevProps, prevState) 来返回一个布尔值,当返回值为 true 时才会触发 render。

而如果我们使用 React.memo,我们可以传递一个比较函数作为第二个参数。

**注意!**React.memo 的第二参数(比较函数)和 shouldComponentUpdate 的逻辑是相反的,只有当返回值为 false 的时候才会触发 render。参考文档

const Bar = React.memo(
  function Bar({ name: { first, last } }) {
    console.log("update");
    return (
      <h1>
        {first} {last}
      </h1>
    );
  },
  (prevProps, newProps) =>
    prevProps.name.first === newProps.name.first &&
    prevProps.name.last === newProps.name.last
);

尽管这条建议是可行的,但我们仍要注意比较函数的性能开销。如果 props 对象过深,反而会消耗不少的性能。

总结

上述场景仍不够全面,但多少能带来一些启发性思考。当然在性能方面,我们还有许多其他的问题需要考虑,但遵守上述的准则仍能带来相当不错的性能提升。

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.