Git Product home page Git Product logo

Comments (5)

Henry-Diasa avatar Henry-Diasa commented on July 23, 2024

数据管理

setState原理(过时)

image

redux的state是如何注入的(connect)

image

react中的props为什么是只读的?

image

getDerivedStateFromProps

image

React中检验props

image

from awesome_interview_question.

Henry-Diasa avatar Henry-Diasa commented on July 23, 2024

生命周期

生命周期有哪些

image
image

React废弃了哪些生命周期?

image

from awesome_interview_question.

Henry-Diasa avatar Henry-Diasa commented on July 23, 2024

组件通信

通信的方式

image

路由

路由实现原理

image

Link标签和a标签的区别

image

redux

工作原理

image

中间件

image

redux状态管理和window上的区别

image

mobx和redux

image

redux中间件怎么拿到store和action的

image

redux中connect的作用

image

from awesome_interview_question.

Henry-Diasa avatar Henry-Diasa commented on July 23, 2024

hooks

为什么useState使用数组而不是对象

image

hooks解决了什么问题

image

useEffect 和 useLayoutEffect

image

react与vue的diff算法的区别

image

其他

react数据持久化实践

redux-persist

react和vue的不同

image

react中的props.children和react.children区别

image

from awesome_interview_question.

Henry-Diasa avatar Henry-Diasa commented on July 23, 2024

hooks原理

二 hooks与fiber(workInProgress)

之前章节讲过,类组件的状态比如 state ,context ,props 本质上是存在类组件对应的 fiber 上,包括生命周期比如 componentDidMount ,也是以副作用 effect 形式存在的。那么 Hooks 既然赋予了函数组件如上功能,所以 hooks 本质是离不开函数组件对应的 fiber 的。 hooks 可以作为函数组件本身和函数组件对应的 fiber 之间的沟通桥梁。

hook1.jpg

hooks 对象本质上是主要以三种处理策略存在 React 中:

  • 1 ContextOnlyDispatcher: 第一种形态是防止开发者在函数组件外部调用 hooks ,所以第一种就是报错形态,只要开发者调用了这个形态下的 hooks ,就会抛出异常。
  • 2 HooksDispatcherOnMount: 第二种形态是函数组件初始化 mount ,因为之前讲过 hooks 是函数组件和对应 fiber 桥梁,这个时候的 hooks 作用就是建立这个桥梁,初次建立其 hooks 与 fiber 之间的关系。
  • 3 HooksDispatcherOnUpdate:第三种形态是函数组件的更新,既然与 fiber 之间的桥已经建好了,那么组件再更新,就需要 hooks 去获取或者更新维护状态。

一个 hooks 对象应该长成这样:

const HooksDispatcherOnMount = { /* 函数组件初始化用的 hooks */
    useState: mountState,
    useEffect: mountEffect,
    ...
}
const  HooksDispatcherOnUpdate ={/* 函数组件更新用的 hooks */
   useState:updateState,
   useEffect: updateEffect,
   ...
}
const ContextOnlyDispatcher = {  /* 当hooks不是函数内部调用的时候,调用这个hooks对象下的hooks,所以报错。 */
   useEffect: throwInvalidHookError,
   useState: throwInvalidHookError,
   ...
}

函数组件触发

所有函数组件的触发是在 renderWithHooks 方法中,在 fiber 调和过程中,遇到 FunctionComponent 类型的 fiber(函数组件),就会用 updateFunctionComponent 更新 fiber ,在 updateFunctionComponent 内部就会调用 renderWithHooks 。

react-reconciler/src/ReactFiberHooks.js

let currentlyRenderingFiber
function renderWithHooks(current,workInProgress,Component,props){
    currentlyRenderingFiber = workInProgress;
    workInProgress.memoizedState = null; /* 每一次执行函数组件之前,先清空状态 (用于存放hooks列表)*/
    workInProgress.updateQueue = null;    /* 清空状态(用于存放effect list) */
    ReactCurrentDispatcher.current =  current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate /* 判断是初始化组件还是更新组件 */
    let children = Component(props, secondArg); /* 执行我们真正函数组件,所有的hooks将依次执行。 */
    ReactCurrentDispatcher.current = ContextOnlyDispatcher; /* 将hooks变成第一种,防止hooks在函数组件外部调用,调用直接报错。 */
}

workInProgress 正在调和更新函数组件对应的 fiber 树。

  • 对于类组件 fiber ,用 memoizedState 保存 state 信息,对于函数组件 fiber ,用 memoizedState 保存 hooks 信息
  • 对于函数组件 fiber ,updateQueue 存放每个 useEffect/useLayoutEffect 产生的副作用组成的链表。在 commit 阶段更新这些副作用。
  • 然后判断组件是初始化流程还是更新流程,如果初始化用 HooksDispatcherOnMount 对象,如果更新用 HooksDispatcherOnUpdate 对象。函数组件执行完毕,将 hooks 赋值给 ContextOnlyDispatcher 对象。引用的 React hooks都是从 ReactCurrentDispatcher.current 中的, React 就是通过赋予 current 不同的 hooks 对象达到监控 hooks 是否在函数组件内部调用。
  • Component ( props , secondArg ) 这个时候函数组件被真正的执行,里面每一个 hooks 也将依次执行。
  • 每个 hooks 内部为什么能够读取当前 fiber 信息,因为 currentlyRenderingFiber ,函数组件初始化已经把当前 fiber 赋值给 currentlyRenderingFiber ,每个 hooks 内部读取的就是 currentlyRenderingFiber 的内容。

hooks初始化- hooks 如何和 fiber 建立起关系

hooks 初始化流程使用的是 mountState,mountEffect 等初始化节点的hooks,将 hooks 和 fiber 建立起联系,那么是如何建立起关系呢,每一个hooks 初始化都会执行 mountWorkInProgressHook ,接下来看一下这个函数。

react-reconciler/src/ReactFiberHooks.js

function mountWorkInProgressHook() {
  const hook = {  memoizedState: null, baseState: null, baseQueue: null,queue: null, next: null,};
  if (workInProgressHook === null) {  // 只有一个 hooks
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {  // 有多个 hooks
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

首先函数组件对应 fiber 用 memoizedState 保存 hooks 信息,每一个 hooks 执行都会产生一个 hooks 对象,hooks 对象中,保存着当前 hooks 的信息,不同 hooks 保存的形式不同。每一个 hooks 通过 next 链表建立起关系。

假设在一个组件中这么写

export default function Index(){
    const [ number,setNumber ] = React.useState(0) // 第一个hooks
    const [ num, setNum ] = React.useState(1)      // 第二个hooks
    const dom = React.useRef(null)                 // 第三个hooks
    React.useEffect(()=>{                          // 第四个hooks
        console.log(dom.current)
    },[])
    return <div ref={dom} >
        <div onClick={()=> setNumber(number + 1 ) } > { number } </div>
        <div onClick={()=> setNum(num + 1) } > { num }</div>
    </div>
}

那么如上四个 hooks ,初始化,每个 hooks 内部执行 mountWorkInProgressHook ,然后每一个 hook 通过 next 和下一个 hook 建立起关联,最后在 fiber 上的结构会变成这样。

效果:

hook2.jpg

hooks更新

更新 hooks 逻辑和之前 fiber 章节中讲的双缓冲树更新差不多,会首先取出 workInProgres.alternate 里面对应的 hook ,然后根据之前的 hooks 复制一份,形成新的 hooks 链表关系。这个过程中解释了一个问题,就是hooks 规则,hooks 为什么要通常放在顶部,hooks 不能写在 if 条件语句中,因为在更新过程中,如果通过 if 条件语句,增加或者删除 hooks,在复用 hooks 过程中,会产生复用 hooks 状态和当前 hooks 不一致的问题。举一个例子,还是将如上的 demo 进行修改。

将第一个 hooks 变成条件判断形式,具体如下:

export default function Index({ showNumber }){
    let number, setNumber
    showNumber && ([ number,setNumber ] = React.useState(0)) // 第一个hooks
}

第一次渲染时候 showNumber = true 那么第一个 hooks 会渲染,第二次渲染时候,父组件将 showNumber 设置为 false ,那么第一个 hooks 将不执行,那么更新逻辑会变成这样。

hook复用顺序 缓存的老hooks 新的hooks
第一次hook复用 useState useState
第二次hook复用 useState useRef

hook3.jpeg

第二次复用时候已经发现 hooks 类型不同 useState !== useRef ,那么已经直接报错了。所以开发的时候一定注意 hooks 顺序一致性。

报错内容:

hookk4.jpg

三 状态派发

useState 解决了函数组件没有 state 的问题,让无状态组件有了自己的状态,useState 在 state 章节已经说了基本使用,接下来重点介绍原理使用, useState 和 useReducer 原理大同小异,本质上都是触发更新的函数都是 dispatchAction。

比如一段代码中这么写:

const [ number,setNumber ] = React.useState(0)  

setNumber 本质就是 dispatchAction 。首先需要看一下执行 useState(0) 本质上做了些什么?

react-reconciler/src/ReactFiberHooks.js

function mountState(initialState){
     const hook = mountWorkInProgressHook();
    if (typeof initialState === 'function') {initialState = initialState() } // 如果 useState 第一个参数为函数,执行函数得到初始化state
     hook.memoizedState = hook.baseState = initialState;
    const queue = (hook.queue = { ... }); // 负责记录更新的各种状态。
    const dispatch = (queue.dispatch = (dispatchAction.bind(  null,currentlyRenderingFiber,queue, ))) // dispatchAction 为更新调度的主要函数 
    return [hook.memoizedState, dispatch];
}
  • 上面的 state 会被当前 hooks 的 memoizedState 保存下来,每一个 useState 都会创建一个 queue 里面保存了更新的信息。
  • 每一个 useState 都会创建一个更新函数,比如如上的 setNumber 本质上就是 dispatchAction,那么值得注意一点是,当前的 fiber 被 bind 绑定了固定的参数传入 dispatchAction 和 queue ,所以当用户触发 setNumber 的时候,能够直观反映出来自哪个 fiber 的更新。
  • 最后把 memoizedState dispatch 返回给开发者使用。

接下来重点研究一下 dispatchAction ,底层是怎么处理更新逻辑的。

function dispatchAction(fiber, queue, action){
    /* 第一步:创建一个 update */
    const update = { ... }
    const pending = queue.pending;
    if (pending === null) {  /* 第一个待更新任务 */
        update.next = update;
    } else {  /* 已经有带更新任务 */
       update.next = pending.next;
       pending.next = update;
    }
    if( fiber === currentlyRenderingFiber ){
        /* 说明当前fiber正在发生调和渲染更新,那么不需要更新 */
    }else{
       if(fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)){
            const lastRenderedReducer = queue.lastRenderedReducer;
            const currentState = queue.lastRenderedState;                 /* 上一次的state */
            const eagerState = lastRenderedReducer(currentState, action); /* 这一次新的state */
            if (is(eagerState, currentState)) {                           /* 如果每一个都改变相同的state,那么组件不更新 */
               return 
            }
       }
       scheduleUpdateOnFiber(fiber, expirationTime);    /* 发起调度更新 */
    }
}

原来当每一次改变 state ,底层会做这些事。

  • 首先用户每一次调用 dispatchAction(比如如上触发 setNumber )都会先创建一个 update ,然后把它放入待更新 pending 队列中。
  • 然后判断如果当前的 fiber 正在更新,那么也就不需要再更新了。
  • 反之,说明当前 fiber 没有更新任务,那么会拿出上一次 state 和 这一次 state 进行对比,如果相同,那么直接退出更新。如果不相同,那么发起更新调度任务。这就解释了,为什么函数组件 useState 改变相同的值,组件不更新了。

接下来就是更新的环节,下面模拟一个更新场景。

export default  function Index(){
    const [ number , setNumber ] = useState(0)
    const handleClick=()=>{
        setNumber(num=> num + 1 ) // num = 1
        setNumber(num=> num + 2 ) // num = 3 
        setNumber(num=> num + 3 ) // num = 6
    }
    return <div>
        <button onClick={() => handleClick() } >点击 { number } </button>
    </div>
}
  • 如上当点击一次按钮,触发了三次 setNumber,等于触发了三次 dispatchAction ,那么这三次 update 会在当前 hooks 的 pending 队列中,然后事件批量更新的概念,会统一合成一次更新。接下来就是组件渲染,那么就到了再一次执行 useState,此时走的是更新流程。那么试想一下点击 handleClick 最后 state 被更新成 6 ,那么在更新逻辑中 useState 内部要做的事,就是得到最新的 state 。
function updateReducer(){
    // 第一步把待更新的pending队列取出来。合并到 baseQueue
    const first = baseQueue.next;
    let update = first;
   do {
        /* 得到新的 state */
        newState = reducer(newState, action);
    } while (update !== null && update !== first);
     hook.memoizedState = newState;
     return [hook.memoizedState, dispatch];
}
  • 当再次执行useState的时候,会触发更新hooks逻辑,本质上调用的就是 updateReducer,如上会把待更新的队列 pendingQueue 拿出来,合并到 baseQueue,循环进行更新。
  • 循环更新的流程,说白了就是执行每一个 num => num + 1 ,得到最新的 state 。接下来就可以从 useState 中得到最新的值。

用一幅图来描述整个流程。

hook5.jpg

四 处理副作用

初始化

在 fiber 章节讲了,在 render 阶段,实际没有进行真正的 DOM 元素的增加,删除,React 把想要做的不同操作打成不同的 effectTag ,等到commit 阶段,统一处理这些副作用,包括 DOM 元素增删改,执行一些生命周期等。hooks 中的 useEffect 和 useLayoutEffect 也是副作用,接下来以 effect 为例子,看一下 React 是如何处理 useEffect 副作用的。

下面还是以初始化和更新两个角度来分析。

function mountEffect(create,deps){
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    currentlyRenderingFiber.effectTag |= UpdateEffect | PassiveEffect;
    hook.memoizedState = pushEffect( 
      HookHasEffect | hookEffectTag, 
      create, // useEffect 第一次参数,就是副作用函数
      undefined, 
      nextDeps, // useEffect 第二次参数,deps    
    )
}
  • mountWorkInProgressHook 产生一个 hooks ,并和 fiber 建立起关系。
  • 通过 pushEffect 创建一个 effect,并保存到当前 hooks 的 memoizedState 属性下。
  • pushEffect 除了创建一个 effect , 还有一个重要作用,就是如果存在多个 effect 或者 layoutEffect 会形成一个副作用链表,绑定在函数组件 fiber 的 updateQueue 上。

为什么 React 会这么设计呢,首先对于类组件有componentDidMount/componentDidUpdate 固定的生命周期钩子,用于执行初始化/更新的副作用逻辑,但是对于函数组件,可能存在多个 useEffect/useLayoutEffect ,hooks 把这些 effect,独立形成链表结构,在 commit 阶段统一处理和执行。

如果在一个函数组件中这么写:

React.useEffect(()=>{
    console.log('第一个effect')
},[ props.a ])
React.useLayoutEffect(()=>{
    console.log('第二个effect')
},[])
React.useEffect(()=>{
    console.log('第三个effect')
    return () => {}
},[])

那么在 updateQueue 中,副作用链表会变成如下样子:

hook6.jpg

更新

更新流程对于 effect 来说也很简单,首先设想一下 useEffect 更新流程,无非判断是否执行下一次的 effect 副作用函数。还有一些细枝末节。

function updateEffect(create,deps){
    const hook = updateWorkInProgressHook();
    if (areHookInputsEqual(nextDeps, prevDeps)) { /* 如果deps项没有发生变化,那么更新effect list就可以了,无须设置 HookHasEffect */
        pushEffect(hookEffectTag, create, destroy, nextDeps);
        return;
    } 
    /* 如果deps依赖项发生改变,赋予 effectTag ,在commit节点,就会再次执行我们的effect  */
    currentlyRenderingFiber.effectTag |= fiberEffectTag
    hook.memoizedState = pushEffect(HookHasEffect | hookEffectTag,create,destroy,nextDeps)
}

更新 effect 的过程非常简单。

  • 就是判断 deps 项有没有发生变化,如果没有发生变化,更新副作用链表就可以了;如果发生变化,更新链表同时,打执行副作用的标签:fiber => fiberEffectTag,hook => HookHasEffect。在 commit 阶段就会根据这些标签,重新执行副作用。

不同的effect

关于 EffectTag 的思考🤔:

  • React 会用不同的 EffectTag 来标记不同的 effect,对于useEffect 会标记 UpdateEffect | PassiveEffect, UpdateEffect 是证明此次更新需要更新 effect ,HookPassive 是 useEffect 的标识符,对于 useLayoutEffect 第一次更新会打上 HookLayout 的标识符。React 就是在 commit 阶段,通过标识符,证明是 useEffect 还是 useLayoutEffect ,接下来 React 会同步处理 useLayoutEffect ,异步处理 useEffect

  • 如果函数组件需要更新副作用,会标记 UpdateEffect,至于哪个effect 需要更新,那就看 hooks 上有没有 HookHasEffect 标记,所以初始化或者 deps 不想等,就会给当前 hooks 标记上 HookHasEffect ,所以会执行组件的副作用钩子。

五 状态获取与状态缓存

1 对于 ref 处理

在 ref 章节详细介绍过,useRef 就是创建并维护一个 ref 原始对象。用于获取原生 DOM 或者组件实例,或者保存一些状态等。

创建:

function mountRef(initialValue) {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref; // 创建ref对象。
  return ref;
}

更新:

function updateRef(initialValue){
  const hook = updateWorkInProgressHook()
  return hook.memoizedState // 取出复用ref对象。
}

如上 ref 创建和更新过程,就是 ref 对象的创建和复用过程。

2 对于useMemo的处理

对于 useMemo ,逻辑比 useRef 复杂点,但是相对于 useState 和 useEffect 简单的多。

创建:

function mountMemo(nextCreate,deps){
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
  • useMemo 初始化会执行第一个函数得到想要缓存的值,将值缓存到 hook 的 memoizedState 上。

更新:

function updateMemo(nextCreate,nextDeps){
    const hook = updateWorkInProgressHook();
    const prevState = hook.memoizedState; 
    const prevDeps = prevState[1]; // 之前保存的 deps 值
    if (areHookInputsEqual(nextDeps, prevDeps)) { //判断两次 deps 值
        return prevState[0];
    }
    const nextValue = nextCreate(); // 如果deps,发生改变,重新执行
    hook.memoizedState = [nextValue, nextDeps];
    return nextValue;
}
  • useMemo 更新流程就是对比两次的 dep 是否发生变化,如果没有发生变化,直接返回缓存值,如果发生变化,执行第一个参数函数,重新生成缓存值,缓存下来,供开发者使用。*

react18下的自动批量更新

在18的版本中,react实现了自动的批量处理,内部实现的原理很简单,通过事件的循环实现。即将当前事件循环的宏任务中所有setState更新需要的数据挂载到对应的fiber上,并将第一个setState产生的performWorkOnRoot方法放在本次循环的微任务或者下一次循环的宏任务中执行,当前循环宏任务的其他setState只产生数据,不执行performWorkOnRoot,数据的更新都在微任务或者下次宏任务中批量更新一次。这样即实现了setState的批量更新。详细过程看代码注释。

// setState
enqueueSetState: function (inst, payload, callback) {
    ...
    // 获取优先级,一般在同一个宏任务执行的setState都会返回相同的lane,
    // 在一个宏任务中的多个setState根据相同的lane实现批量更新
    var lane = requestUpdateLane(fiber);
    var update = createUpdate(eventTime, lane); // 创建更新信息
    update.payload = payload;
    ...
    // 将更新数据挂在fiber上,此处update是通过链表结构添加到原有update.next上
    var root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, lane, eventTime); // 调度进行更新
      ...
    }
    ...
  },
  
 function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
     ...
     ensureRootIsScheduled(root, eventTime);
     // 此处和17版本的差别,17版本中不判断fiber.mode,导致异步内的setState如果没有使用batchupdates方法包裹去设置executionContext的值,会进入该分支依次执行syncQueue的任务
     if (lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode && ( ReactCurrentActQueue$1.isBatchingLegacy)) {
      resetRenderTimer();
      flushSyncCallbacksOnlyInLegacyMode();
    }
     ...
 }
 
 function ensureRootIsScheduled(root, currentTime) {
    var existingCallbackNode = root.callbackNode; // 获取暂停的任务
    ...
    // 即将执行任务的lanes
    var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
    ...
    var newCallbackPriority = getHighestPriorityLane(nextLanes); // 获取任务中最高的优先级
    var existingCallbackPriority = root.callbackPriority; // 暂停任务的优先级
    // 暂停任务优先级与即将执行任务的优先级相同,此时退出不再向下执行,
    // 执行到此处,该次setState更新正确结果所需要的数据已存储在对应的fibere
    if (existingCallbackPriority === newCallbackPriority && !( ReactCurrentActQueue$1.current !== null && existingCallbackNode !== fakeActCallbackNode)) {
    {
    ...
    return;
  }
  ...
  var newCallbackNode;
  /* newCallbackPriority === 1,点击等事件会走这个分支
  ** handleClick = () => {
  **   setState();
  **   setState();
  ** }
  */
  if (newCallbackPriority === SyncLane) {
    if (root.tag === LegacyRoot) {
      if ( ReactCurrentActQueue$1.isBatchingLegacy !== null) {
        ReactCurrentActQueue$1.didScheduleLegacyUpdate = true;
      }
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
    // 将performSyncWorkOnRoot添加到syncQueue
    // 相同优先级之添加一次,其他次已在上面判断中return出去了
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    {
      if ( ReactCurrentActQueue$1.current !== null) {
        ...
      } else {
      // 创建一个微任务,在该次事件循环的宏任务结束后执行
        scheduleMicrotask(function () {
          if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
           // 依次执行syncQueue的任务
            flushSyncCallbacks();
          }
        });
      }
    }
    newCallbackNode = null;
  } else {
     /* 异步,微任务获取优先级不为1的走该分支
      ** setTimeout(() => {
      **   setState();
      **   setState();
      ** }, 0)
      */
    var schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
       ...
    }
    // scheduleCallback$1方法调用scheduler的scheduleCallback方法
    // 该方法最终执行port.postmessage,
    // 在监听message变化时,即下次事件循环中执行performConcurrentWorkOnRoot方法
    // 在本次事件循环宏任务中所有的setState更新都这次的performConcurrentWorkOnRoot执行
    newCallbackNode = scheduleCallback$1(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

from awesome_interview_question.

Related Issues (20)

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.