深入SWR 设计与源码分析

发表于 2年以前  | 总阅读数:399 次

1 前言

SWRNext.jsReact SSR框架)背后的同一团队创建。号称最牛逼的React 数据请求库

SWR:是stale-while-revalidate的缩写 ,源自 HTTP Cache-Control 协议中的 stale-while-revalidate 指令规范。也算是HTTP缓存策略的一种,这种策略首先消费缓存中旧(stale)的数据,同时发起新的请求(revalidate),当返回数据的时候用最新的数据替换运行的数据。数据的请求和替换的过程都是异步的,对于用户来说无需等待新请求返回时才能看到数据。

SWR的缓存策略:

  • 接受一个缓存key,同一个 key 在缓存有效期内发起的请求,会走 SWR 策略

  • 在一定时间内,同一个key发起多个请求,SWR库会做节流,只会有一个请求真正发出去

举个官网的简单列子

import useSWR from 'swr'

function Profile() {
  const { data, error, isValidating, mutate } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

这个例子是前端较为基础的请求,通过使用useSWR实现了简单明了的请求,当然它还有很多更强大的功能。

2 基础用法

2.1 useSWR

const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)

2.1.1 参数

useSWR 接受三个参数:一个 key 、一个异步请求函数 fetch 和一个 config 配置 。

  • key: 请求的唯一key string(或者是 function / array / null) 是数据的唯一标识符,标识数据请求,通常是 API URL,并且 fetch 接受 key 作为其参数。

  • key为函数function或者null:可以用来有条件地请求数据实现按需请求,当函数跑出错误或者falsy值时,SWR将不会发起请求。

  // 有条件的请求
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)

// ...或返回一个 falsy 值
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher)

// ... 或在 user.id 未定义时抛出错误
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)
  • 依赖请求场景:当需要一段动态数据才能进行下一次数据请求时,它可以确保最大程度的并行性(avoiding waterfalls)以及串行请求。

  • function MyProjects () {
    const { data: user } = useSWR('/api/user')
    const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id)
    // 传递函数时,SWR 会用返回值作为 `key`。
    // 如果函数抛出错误或返回 falsy 值,SWR 会知道某些依赖还没准备好。
    // 这种情况下,当 `user`未加载时,`user.id` 抛出错误
    
    if (!projects) return 'loading...'
    return 'You have ' + projects.length + ' projects'
    }
  • fetcher(args): 返回数据的异步函数,接受 key 做参数并返回数据,你可以使用原生的 fetchAxios 之类的工具。

  • config

2.1.2 返回值

  • data: 通过 fetcher 用给定的 key 获取的数据(如未完全加载,返回 undefined,这时可以用来做一些loading态)

  • error: fetcher 抛出的错误(或者是undefined

  • isValidating: 是否有请求或重新验证加载

  • mutate(data?, shouldRevalidate?): 更改缓存数据的函数,可以在数据更改发起数据重新验证的场景

可以使用 useSWRConfig() 所返回的 mutate 函数,来广播重新验证的消息给其他的 SWR hook(*)。使用同一个 key 调用 mutate(key) 即可。以下示例显示了当用户点击 “注销” 按钮时如何自动重新请求登录信息

import useSWR, { useSWRConfig } from 'swr'

function App () {
  const { mutate } = useSWRConfig()
  return (
    <div>
      <Profile />
      <button onClick={() => {
        // 将 cookie 设置为过期
        document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'

        // 告诉所有具有该 key 的 SWR 重新验证
        mutate('/api/user')
      }}>
        Logout
      </button>
    </div>
  )
}

通常情况下 mutate 会广播给同一个 cache provider 下面的 SWR hooks 。如果没有设置 cache provider ,即会广播给所有的 SWR hooks

2.2 核心特性

通过 SWR 官网介绍,SWR 有以下比较亮点特性

  • 极速、轻量、可重用的 数据请求

  • 内置 缓存 和重复请求去除

  • 间隔轮询

  • 聚焦时、网络恢复时重新验证

  • 本地缓存更新

  • 智能错误重试

  • 分页和滚动位置恢复

3 核心功能拆解实现

虽然 SWR特性很多,功能很强大,能极大提升用户体验,但是 SWR 却使用很简洁的思路完成上述所有的功能,并通过一个hook useSWR 即可拥有几乎全部功能。即功能强大,api使用却十分简单,开发体验十分喜人。

上面说的 SWR 更多是功能特性,体验优化。那么站在技术的角度,SWR 又是扮演什么角色?

  • 纯纯的 hook 请求库

  • 全局状态管理

  • 数据缓存

  • 自动化重新数据验证(轮询、断网重连、页面聚焦等)

下面我们拆解他的功能,按照 SWR 一样的思路代码实现相同

3.1 hooks 请求库

useSWR 是一个 react hook,通过这个 hook 你可以获取数据,并在数据获取后,触发页面重新渲染,这是 hook 基本特性,平平无奇,react``useState + promise 就能轻松实现。

function useSwr(key, fetcher) {
  const [state, setState] = useState({})
  const revalidate = useCallback(async () => {
    try {
      const result = await fetcher(key)
      setState({
        error: undefined,
        data: result
      })
    } catch (error) {
      setState({
        ...state,
        error
      })
    }
  })

  useEffect(() => {
    revalidate()
  }, [key])
  return { data, error }
}

有点简单,也的确没亮点,虽然 SWR 里面允许 keystringarray 甚至 function。但觉得这些都不是亮点。但是.... SWR 有一个小操作,有点按需更新的意思

function App() {
  // const { data, error, isValidating } = useSWR('/api/user', fetcher)
  const { data } = useSWR('/api/user', fetcher)
  return <div>hello {data.name}!</div>
}

useSWR 会返回 data, error, isValidating,只要有一个变化页面就会重新渲染。可页面只用到 data, 是否可以 仅仅 data 更改时候才触发重新渲染呢?

SWR 做了一个操作,有点 vue mvvm 模型的意思(settergetter 在脑海里琅琅上口)。ReactsetState会触发更新,直接使用肯定不行,SWR 就封装了一下,SWR 是在 state.js里面实现该逻辑

function useStateWithDeps(state) {
  const stateRef = useRef(state)

  //用于存储哪些属性被订阅
  const stateDependenciesRef = useRef({
    data: false,
    error: false,
    isValidating: false
  })

  const rerender = useState({})[1]

  const setState = useCallback((payload) => {
    let shouldRerender = false

    const currentState = stateRef.current
    for (const k in payload) {
      // 是否有变化
      if (currentState[k] !== payload[k]) {
        currentState[k] = payload[k]

        // 是否有被使用
        if (stateDependenciesRef.current[k]) {
          shouldRerender = true
        }
      }
    }

    if (shouldRerender && !unmountedRef.current) {
      rerender({})
    }
  })
  useEffect(() => {
    stateRef.current = state
  })

  return [stateRef, stateDependenciesRef.current, setState]
}

// 如果单纯设计 stateDependenciesRef,可以把setter、getter 写在 useStateWithDeps 里面。但use 并没有直接暴露 stateDependenciesRef,而是暴露 useSwr。所以把数据劫持放在 useSwr

function useSwr(key, fetcher) {
  //......

  const [stateRef, stateDependencies, setState] = useStateWithDeps({
    data,
    error,
    isValidating
  })

  return {
    get data() {
      stateDependencies.data = true
      return data
    },
    get error() {
      stateDependencies.error = true
      return error
    },
    get isValidating() {
      stateDependencies.isValidating = true
      return isValidating
    }
  }
}

3.2 全局状态管理

SWR 可不是简单管理一个组件的状态,而是组件之间相同 key 直接的数据是可以保持同步刷新,牵一发而动全身。ReactuseState 使用就是只会触发使用组件的重新渲染,即谁用我,我就更新谁。那么如何做到组件之间,一个地方修改,所有地方都能触发重新渲染。

下面演示,精简版的 React 全局状态库数据管理的实现。SWR 底层逻辑与之不谋而合

import { useState, useEffect } from 'react'
//全局数据存储
let data = {}
//发布订阅机制
const listeners = []
function broadcastState(state) {
  data = {
    ...data,
    ...state,
  }
  listeners.forEach((listener) => listener(data))
}

const useData = () => {
  const [state, setState] = useState(data)
  function handleChange(payload) {
    setState({
      ...state,
      ...payload,
    })
    broadcastState(payload)
  }

  useEffect(() => {
    listeners.push(handleChange)
    return () => {
      listeners.splice(listeners.indexOf(handleChange), 1)
    }
  }, [])
  return [state, handleChange]
}

export default useData

3.3 数据缓存

在上面讲到全局状态时候,我们定义了一个 data 存储了数据,在 SWR 底层,则是采用一个 weakMap 存储数据,道理相似。

SWR 是一个请求库,对于数据存储,并不是直接存储 Data, 而是存储 Promise<Data>

充分利用promise 状态一旦更改就不会变的特性,也十分适合异步数据请求

//区分 key,可以理解为安置 key 管理
const globalState = new Map({
  //更新数据事件,即上文中的 listeners
  STATE_UPDATERS: {}, //[key:callbacks]
  //重新获取数据事件
  EVENT_REVALIDATORS: {}, //[key:callbacks]
  // 异步数据请求缓存,缓存的是 promise
  FETCH: {}, //[key:callbacks]
})

function useSwr(key, fetcher) {
  //...

  const [stateRef, stateDependencies, setState] = useStateWithDeps(cacheInfo)
  //获取数据函数
  const revalidate = async () => {
    if (cache) {

    } else {
      // 没有 await
      const fetch=fetcher(...args)
      setCache(key,fetch)
    }

  }
}

3.4 自动化重新数据验证(轮询、断网重连、页面聚焦等)

3.4.1 轮询数据

即间隔固定时间,重新发送请求,更新数据

function useSwr(key,fetcher) {
  //...

  // Polling
  useEffect(() => {
    let timer
    function next() {
      timer = setTimeout(execute, interval)
    }
    function execute() {
      revalidate().then(next)
    }

    next()

    return () => {
      if (timer) {
        clearTimeout(timer)
        timer = -1
      }
    }
  }, [interval])
}

3.4.2 断网重连、页面聚焦重新请求

也是如同上文的的全局状态管理,在使用 useSwr 时候把重新获取数据的函数(事件)推送到全局的数据存储里面,然后订阅浏览器事件,并从全局数据存储里面读取事件执行

//subscribe-key.js
function subscribeCallback(events, callback) {
  events.push(callback)
  return () => {
    const index = events.indexOf(callback)
    // 释放事件
    if (index >= 0) {
      // O(1): faster than splice
      events[index] = events[events.length - 1]
      events.pop()
    }
  }
}

 // useSwr.js
function useSwr(key, fetcher) {
  //...

  //获取数据函数
  const revalidate = async () => {
    //...fetcher()
  }

  useEffect(() => {
    // 更新数据,推入队列,确保其他组件更新数据,能通过 broadcastState 触发当前组件更新
    const onStateUpdate = () => {
      //... setState()
    }
    // 重新刷新数据,在一些网络恢复、聚焦时候执行
    const onRevalidate = () => {
      //... revalidate()
    }
    const unsubUpdate = subscribeCallback(key, STATE_UPDATERS, onStateUpdate)
    const unsubEvents = subscribeCallback(key, EVENT_REVALIDATORS, onRevalidate)
    return () => {
      unsubUpdate()
      unsubEvents()
    }
  }, [key, revalidate])

  //...
}

浏览器订阅事件如下

useEffect 统一监听浏览器事件即可

// web-preset.js
const onWindowEvent =window.addEventListener
const onDocumentEvent = document.addEventListener.bind(document)
const offWindowEvent =window.removeEventListener.bind(window)
const offDocumentEvent =document.removeEventListener.bind(document)

const initFocus = (callback) => {
  // 页面重新聚焦 重新获取数据
  onDocumentEvent('visibilitychange', callback)
  onWindowEvent('focus', callback)
  return () => {
    offDocumentEvent('visibilitychange', callback)
    offWindowEvent('focus', callback)
  }
}

const initReconnect = (callback) => {
  // 网络恢复,重新获取数据
  const onOnline = () => {
    online = true
    callback()
  }
  // nothing to revalidate, just update the status
  const onOffline = () => {
    online = false
  }
  onWindowEvent('online', onOnline)
  onWindowEvent('offline', onOffline)
  return () => {
    offWindowEvent('online', onOnline)
    offWindowEvent('offline', onOffline)
  }
}

3.5 其他

3.5.1 全局配置

useSWR(key, fetcher, options)options支持需要配置属性,那么如果期望在某个范围内,所有的hook,共用一套配置如何实现呢。SWR 提供一个组件叫 SwrConfig

import useSWR, { SWRConfig } from 'swr'

function App () {
  return (
    <SWRConfig 
      value={{
        refreshInterval: 3000,
        fetcher: (resource, init) => fetch(resource, init).then(res => res.json())
      }}
    >
      <Dashboard />
    </SWRConfig>
  )
}

Dashboard 下所有的 useSwr 共用 value 作为配置。

组件提供全局配置的 provider,子组件都共用这个配置,是一种很常见组件的设计思路。主要思路就是利用 react.createContext 提供 ProviderConsumer 能力,不过现在使用 useContext,使用上会比 Consumer 好太多了。

const SWRConfigContext = createContext({})

const ConfigProvider = (props) => {
  // mergeConfigs 会处理中间件 merge逻辑
  // 必须继承上一个 provider SWRConfig 的配置 进行 merge
  const extendedConfig = mergeConfigs(useContext(SWRConfigContext), value)

  return createElement(
    SWRConfigContext.Provider,
    mergeObjects(props, {
      value: extendedConfig, // swr 一些运算处理的配置
    })
  )
}
export const useSWRConfig = () => {
  return mergeConfigs(defaultConfig, useContext(SWRConfigContext))
}

export const SWRConfig = OBJECT.defineProperty(ConfigProvider, 'default', {
  value: defaultConfig,
})

然后在使用中就可以使用全局配置

const fallbackConfig = useSWRConfig()

// 格式化用户入参
const [key, fn, _config] = normalize(args)

const config = mergeConfigs(fallbackConfig, _config)

3.5.2 中间件-洋葱模型

SWR 也支持中间件,让你能够在 SWR hook 之前和之后执行代码。

useSWR(key, fetcher, { use: [a, b, c] })

中间件执行的顺序是 a → b → c,如下所示:

enter a
  enter b
    enter c
      useSWR()
    exit  c
  exit  b
exit  a

那么 swr 是如何实现洋葱模型的呢?代码简单只有10行不到的代码。就是实现一个 compose 逻辑,然后通过函数执行栈一层层嵌套即可,这里有个注意点就是,从最后一个开始嵌套,然后从第一个开始执行。逐层释放执行栈,则刚好是完美洋葱模型的执行顺序。

一个中间件格式如下:

接受上一个 useSwr 这个hook,返回一个新的 hook很符合 compose 函数的思想呀

// Apply middleware
let next = hook //原始的中间件
const { use } = config //中间件列表
if (use) {
  for (let i = use.length; i-- > 0; ) {
    next = use[i](next)
  }
}

return next(key, fn || config.fetcher, config)

3.5.3 请求时序问题处理

这个其实逻辑很简单,但却很关键,所以也在这说明一下

假设我们对一个 key,发了2个请求req1req2。发出的顺序和数据返回数据如下

//   req1------------------>res1        (current one)
        //        req2---------------->res2
因为 req2 发出的事件比较晚,那么我们页面展示的数据应该

因为 req2 发出的事件比较晚,那么我们页面展示的数据应该是以 res2。即始终只更新最晚一次请求的返回值,即 req2 的返回值(这里就算 res2返回更早也是展示 res2,取决于请求事件)

function useSwr(key, fetcher) {
  const revalidate = async () => {
    FETCH[key] = [currentFetcher(...fnArgs), getTimestamp()]
    ;[newData, startAt] = FETCH[key]
    newData = await newData

    //...

    // 当请求数据返回时候,发现staryAt 不一致,说明有其他同 key 请求已经 发出去
    if (!FETCH[key] || FETCH[key][1] !== startAt) {
      //!(FETCH[key] && FETCH[key][1] == startAt)
      if (shouldStartNewRequest) {
        if (isCurrentKeyMounted()) {
          getConfig().onDiscarded(key)
        }
      }
      return false
    }
  }
}

这里有一个容易疑惑的点就是为何只是判断 startAt 不相等就放弃当前数据更改呢?这是因为 FETCH 是全局缓存,是用 map 存储,实时更新。且 FETCH[key] 始终只存一个请求,一旦不等就说明在此之后有相同的 key 请求被发出。

startAt 这个变量是存储在当前组件的作用域里面,而 FETCH 全局缓存,所有组件共享的数据

3.5.4 工具函数

SWR 里面还有需要工具函数可以学习

3.5.4.1 hash与深比较

SWR 中的hash.js用于哈希 keydata,形成一个字符串,并在深比较函数 compare 通过哈希后字符串判断数据是否有变化,是否需要重新请求、重新渲染

3.5.4.2 参数格式化处理

SWRkey 格式可以是 function / array / null ,也是在统一的 normalize.js 里做处理,如果是 falsy 值,则表示不发请求

4 源码分析

SWR 还有许多 options 配置和功能,比如上轮询间隔、是否启用缓存、是否开重复请求去除、错误重试、超时重试、支持 ssr 等。这些都不影响主流逻辑,下面我们按照上面拆解的核心功能,查看 SWR 源码。

4.1 目录结构

SWR 对把逻辑拆分到一个个文件,通过文件名以及我们上面的分析,很容易猜出文件中的逻辑

├── constants
│   └── revalidate-events.ts
├── index.ts
├── types.ts
├── use-swr.ts
└── utils
    ├── broadcast-state.ts // 组件状态修改通知其他组件渲染
    ├── cache.ts // 缓存,缓存事件:如重新请求、网络恢复等事件
    ├── config-context.ts //全局配置 react context
    ├── config.ts
    ├── env.ts 
    ├── global-state.ts //缓存,搭配 cache 使用
    ├── hash.ts // 对数据hash,形成字符串,用于深比较
    ├── helper.ts
    ├── merge-config.ts
    ├── mutate.ts // 更改缓存
    ├── normalize-args.ts // 格式化入参
    ├── resolve-args.ts //初始化操作,是一个 hoc 逻辑,
    ├── serialize.ts // hash 
    ├── state.ts // 属性按需触发重新渲染 
    ├── subscribe-key.ts // 添加事件订阅
    ├── timestamp.ts
    ├── use-swr-config.ts
    ├── web-preset.ts //浏览器事件:聚焦、网络状态变更
    └── with-middleware.ts //中间件

4.2 核心源码

4.2 核心源码

核心流程图

src/use-swr.ts


function useSwr(args) {
  //...

  const fallbackConfig = useSWRConfig()

  // 格式化用户入参
  const [key, fn, _config] = normalize(args)

  const config = mergeConfigs(fallbackConfig, _config)

  // 读取全局缓存,如数据缓存(promise)、事件缓存
  const [EVENT_REVALIDATORS, STATE_UPDATERS, MUTATION, FETCH] =
    SWRGlobalState.get(cache)

  //当前 key 读取存储,有缓存优先使用缓存数据
  const cached = cache.get(key)
  const data = isUndefined(cached) ? fallback : cached

  const info = cache.get(keyInfo) || {}
  const error = info.error

  //按需更新
  const [stateRef, stateDependencies, setState] = useStateWithDeps({
    data,
    error,
    isValidating,
  })

  //获取数据函数
  const revalidate = async () => {
    const shouldStartNewRequest = !FETCH[key] || !opts.dedupe
    if (!shouldStartNewRequest) {

    } else {
      // 没有 await
      FETCH[key] = [currentFetcher(...fnArgs), getTimestamp()]
    }

    //...
    ;[newData, startAt] = FETCH[key]
    newData = await newData

    //...
    finishRequestAndUpdateState()

    //...

    broadcastState()
  }

  useEffect(() => {
    // 更新数据,推入队列,确保其他组件更新数据,能通过 broadcastState 触发当前组件更新
    const onStateUpdate = () => {
      //... setState()
    }
    // 重新刷新数据,在一些网络恢复、聚焦时候执行
    const onRevalidate = () => {
      //... revalidate()
    }
    const unsubUpdate = subscribeCallback(key, STATE_UPDATERS, onStateUpdate)
    const unsubEvents = subscribeCallback(key, EVENT_REVALIDATORS, onRevalidate)
    return () => {
      unsubUpdate()
      unsubEvents()
    }
  }, [key, revalidate])

  return {
    get data() {
      stateDependencies.data = true
      return data
    },
    get error() {
      stateDependencies.error = true
      return error
    },
    get isValidating() {
      stateDependencies.isValidating = true
      return isValidating
    },
  }
}

5 总结

SWR是 一个很轻的 hook 请求库,能在提升用户体验的前提下,也保证很好的开发体验和很低的开发成本。设计理念也很 React,核心功能的实现逻辑也很简单。通过分析SWR 源码,学习核心功能的实现方式,能有效提升代码逻辑思维。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ljFVnDzq4vVSxaUztswCiA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237227次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录