从 Redux 说起,到手写,再到状态管理

简介: 从 Redux 说起,到手写,再到状态管理

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情


先说结论



  1. Redux 是状态管理库,也是一种架构


  1. Redux 与 React 无关,但它是为了解决 React 组件中状态无法共享而出的一种解决方案


  1. 单纯的 Redux 只是一个状态机, store 中存放了所有的状态 state,要想改变里面的状态 state,只能 dispatch 一个动作


  1. 发出去的 action 需要用 reducer 来处理,传入 state 和 action,返回新的 state


  1. subscribe 方法可以注册回调方法,当 dispatch action 的时候会执行里面的回调


  1. Redux 其实是一个发布订阅模式


  1. Redux 支持 enhancer,enhancer 其实就是一个装饰器模式,传入当前的 createStore,返回一个增强的 createStore


  1. Redux 使用 applyMiddleware 函数支持中间件,它的返回值其实就是一个 enhancer


  1. Redux 的中间件也是一个装饰器模式,传入当前的 dispatch,返回一个增强了的 dispatch


  1. 单纯的 Redux 是没有 View 层的


为什么出现 Redux?



我们默认使用 React 技术栈,当页面少且简单时,完全没必要使用 Redux。Redux 的出现,是为了应对复杂组件的情况。即当组件复杂到三层甚至四层时(如下图),组件 4 想改变组件 1 的状态


image.png


按照 React 的做法,状态提升,将状态提升至同一父组件(在图中为祖父组件)。但层级一多,根组件要管理的 state 就很多了,不方便管理。


所以当初有了 context(React 0.14 确定引入),通过 context 能实现”远房组件“的数据共享。但它也有缺点,使用 context 意味着所有的组件都可以修改 context 里面的状态,就像谁都可以修改共享状态一样,导致程序运行的不可预测,这不是我们想要的

facebook 提出了 Flux 解决方案,它引入了单向数据流的概念(没错,React 没有单向数据流的概念,Redux 是集成了 Flux 的单向数据流理念),架构如下图所示:


image.png


这里不表 Flux。简单理解,在 Flux 架构中,View 要通过 Action (动作)通知 Dispatcher(派发器),Dispatcher 来修改 Store,Store 再修改 View


Flux 的问题或者说缺点在哪?


store 之间存在依赖关系、难以进行服务器端渲染、 stores 混杂了逻辑和状态


笔者在学习的 React 技术栈时是 2018 年,那是已然流行 React + Redux 的解决方案,Flux 已经被淘汰了,了解 Flux 是为了引出 Redux


Redux 的出现



Redux 主要解决状态共享问题


官网:Redux 是 JavaScript 状态容器,它提供可预测的状态管理


它的作者是 Dan Abramov


其架构为:


image.png


可以看得出,Redux 只是一个状态机,没有 View 层。其过程可以这样描述:


  • 自己写一个 reducer(纯函数,表示做什么动作会返回什么数据)


  • 自己写一个 initState(store 初始值,可写可不写)


  • 通过 createStore 生成 store,此变量包含了三个重要的属性


  • store.getState:得到唯一值(使用了闭包老哥)


  • store.dispatch:动作行为(改变 store 中数据的唯一指定属性)


  • store.subscribe:订阅(发布订阅模式)


  • 通过 store.dispatch 派发一个 action


  • reducer 处理 action 返回一个新的 store


  • 如果你订阅过,当数据改变时,你会收到通知


按照行为过程,我们可手写一个 Redux,下文在表,先说特点



三大原则


  • 单一数据源


  • 整个应用的 全局 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中


  • State 是只读的


  • 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生时间的普通对象


  • 使用纯函数来执行修改


  • 为了描述 action 如何改变 state tree,你需要编写纯的 reducers


三大原则是为了更好地开发,按照单向数据流的理念,行为变得可回溯


让我们动手写一个 Redux 吧


手写 redux



按照行为过程和原则,我们要避免数据的随意修改、行为的可回溯等问题


基础版:23 行代码让你使用 redux


export const createStore = (reducer, initState) => {
  let state = initState
  let listeners = []
  const subscribe = (fn) => {
    listeners.push(fn)
  }
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach((fn) => fn())
  }
  const getState = () => {
    return state
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}
复制代码


搞个测试用例


import { createStore } from '../redux/index.js'
const initState = {
  count: 0,
}
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      return state
  }
}
const store = createStore(reducer, initState)
store.subscribe(() => {
  let state = store.getState()
  console.log('state', state)
})
store.dispatch({
  type: 'INCREMENT',
})
复制代码


PS:俺是在 node 中使用 ES6 模块,需要升级 Node 版本至 13.2.0


第二版:难点突破:中间件


普通的 Redux 只能做最基础地根据动作返回数据,dispatch 只是一个取数据的命令,例如:


dispatch({
  type: 'INCREMENT',
})
// store 中的 count + 1
复制代码


但在开发中,我们有时候要查看日志、异步调用、记录日常等


怎么办,做成插件


在 Redux 中,类似的概念叫中间件


image.png


Redux 的 createStore 共有三个参数


createStore([reducer], [initial state], [enhancer]);
复制代码


第三个参数为 enhancer,意为增强器。它的作用就是代替普通的 createStore,转变成为附加上中间件的 createStore。打几个比方:


  • 托尼·斯塔克本来是一个普通有钱人,加上增强器(盔甲)后,成了钢铁侠


  • 中央下发一笔救灾款,加上增强器(大小官员的打点)后,到灾民手上的钱只有一丢丢


  • 路飞用武装色打人,武装色就是一个中间件


enhancer 要做的就是:东西还是那个东西,只是经过了一些工序,加强了它。这些工序由 applyMiddleware 函数完成。按照行业术语,它是一个装饰器模式。它的写法大致是:


applyMiddleware(...middlewares)
// 结合 createStore,就是
const store = createStore(reudcer, initState, applyMiddleware(...middlewares))
复制代码


所以我们需要先对 createStore 进行改造,判断当有 enhancer 时,我们需传值给中间件


export const createStore = (reducer, initState, enhancer) => {
    if (enhancer) {
        const newCreateStore = enhancer(createStore)
        return newCreateStore(reducer, initState)
    }
  let state = initState;
    let listeners = [];
    ...
}
复制代码


如果有 enhancer 的话,先传入 createStore 函数,生成的 newCreateStore 和原来的 createStore 一样,会根据 reducer, initState 生成 store。可简化为:


if (enhancer) {
  return enhancer(createStore)(reducer, initState)
}
复制代码


PS:为什么要写成这样,因为 redux 是按照函数式写法来写的


为什么 createStore 可以被传值,因为函数也是对象,也可以作为参数传递(老铁闭包了)


这样我们的 applyMiddleware 自然就明确了


const applyMiddleware = (...middlewares) => {
    return (oldCreateStore) => {
        return (reducer, initState) => {
            const store = oldCreateStore(reducer, initState)
            ...
        }
    }
}
复制代码


这里的 store 表示的是普通版中的 store,接下来我们要增强 store 中的属性


我愿称之为:五行代码让女人为我花了 18 万


export const applyMiddleware = (...middlewares) => {
  return (oldCreateStore) => {
    return (reducer, initState) => {
      const store = oldCreateStore(reducer, initState)
      // 以下为新增
      const chain = middlewares.map((middleware) => middleware(store))
      // 获得老 dispatch
      let dispatch = store.dispatch
      chain.reverse().map((middleware) => {
        // 给每个中间件传入原派发器,赋值中间件改造后的dispatch
        dispatch = middleware(dispatch)
      })
      // 赋值给 store 上的 dispatch
      store.dispatch = dispatch
      return store
    }
  }
}
复制代码


现在写几个中间件来测试一下


// 记录日志
export const loggerMiddleware = (store) => (next) => (action) => {
  console.log('this.state', store.getState())
  console.log('action', action)
  next(action)
  console.log('next state', store.getState())
}
// 记录异常
export const exceptionMiddleware = (store) => (next) => (action) => {
  try {
    next(action)
  } catch (error) {
    console.log('错误报告', error)
  }
}
// 时间戳
export const timeMiddleware = (store) => (next) => (action) => {
  console.log('time', new Date().getTime())
  next(action)
}
复制代码


引入项目中,并运行


import { createStore, applyMiddleware } from '../redux/index.js'
import {
  loggerMiddleware,
  exceptionMiddleware,
  timeMiddleware,
} from './middleware.js'
const initState = {
  count: 0,
}
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      return state
  }
}
const store = createStore(
  reducer,
  initState,
  applyMiddleware(loggerMiddleware, exceptionMiddleware, timeMiddleware),
)
store.subscribe(() => {
  let state = store.getState()
  console.log('state', state)
})
store.dispatch({
  type: 'INCREMENT',
})
复制代码


运行发现已经实现了 redux 最重要的功能——中间件


image.png


来分析下中间件的函数式编程,以 loggerMiddleware 为例:


export const loggerMiddleware = (store) => (next) => (action) => {
  console.log('this.state', store.getState())
  console.log('action', action)
  next(action)
  console.log('next state', store.getState())
}
复制代码


在 applyMiddleware 源码中,


const chain = middlewares.map((middleware) => middleware(store))
复制代码


相当于给每个中间件传值普通版的 store


let dispatch = store.dispatch
chain.reverse().map((middleware) => (dispatch = middleware(dispatch)))
复制代码


相当于给每个中间件在传入 store.dispatch,也就是 next,原 dispatch = next。这个时候的中间件已经本成品了,代码中的 (action) => {...} 就是函数 const dispatch = (action) => {}。当你执行 dispatch({ type: XXX }) 时执行中间件这段(action) => {...}


PS:柯里化一开始比较难理解,用多习惯就慢慢能懂


第三版:结构复杂化与拆分


中间件理解起来或许有些复杂,先看看其他的概念换换思路


一个应用做大后,单靠一个 JavaScript 文件来维护代码显然是不科学的,在 Redux 中,为避免这类情况,它提供了 combineReducers 来整个多个 reducer,使用方法如:


const reducer = combinReducers({
  counter: counterReducer,
  info: infoReducer,
})
复制代码


combinReducers 中传入一个对象,什么样的 state 对应什么样的 reducer。这就好了,那么 combinReducers 怎么实现呢?因为比较简单,不做多分析,直接上源码:


export const combinReducers = (...reducers) => {
  // 拿到 counter、info
  const reducerKey = Object.keys(reducers)
  // combinReducers 合并的是 reducer,返回的还是一个 reducer,所以返回一样的传参
  return (state = {}, action) => {
    const nextState = {}
    // 循环 reducerKey,什么样的 state 对应什么样的 reducer
    for (let i = 0; i < reducerKey.length; i++) {
      const key = reducerKey[i]
      const reducer = reducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
    }
    return nextState
  }
}
复制代码


同级目录下新建一个 reducer 文件夹,并新建 reducer.jsinfo.jsindex.js


// reducer.js
export default (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1,
      }
    case 'DECREMENT': {
      return {
        count: state.count - 1,
      }
    }
    default:
      return state
  }
}
复制代码


// info.js
export default (state, action) => {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name,
      }
    case 'SET_DESCRIPTION':
      return {
        ...state,
        description: action.description,
      }
    default:
      return state
  }
}
复制代码


合并导出


import counterReducer from './counter.js'
import infoReducer from './info.js'
export { counterReducer, infoReducer }
复制代码


我们现在测试一下


import { createStore, applyMiddleware, combinReducers } from '../redux/index.js'
import {
  loggerMiddleware,
  exceptionMiddleware,
  timeMiddleware,
} from './middleware.js'
import { counterReducer, infoReducer } from './reducer/index.js'
const initState = {
  counter: {
    count: 0,
  },
  info: {
    name: 'johan',
    description: '前端之虎',
  },
}
const reducer = combinReducers({
  counter: counterReducer,
  info: infoReducer,
})
const store = createStore(
  reducer,
  initState,
  applyMiddleware(loggerMiddleware, exceptionMiddleware, timeMiddleware),
)
store.dispatch({
  type: 'INCREMENT',
})
复制代码


combinReducers 也完成了


image.png


既然拆分了 reducer,那么 state 是否也能拆分,并且它是否需要传,在我们平时的写法中,一般都不传 state。这里需要两点改造,一是每个 reducer 中包含了它的 state 和 reducer;二是改造 createStore,让 initState 变得可传可不传,以及初始化数据


// counter.js 中写入对应的 state 和 reducer
let initState = {
  counter: {
    count: 0,
  },
}
export default (state, action) => {
  if (!state) {
    state = initState
  }
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1,
      }
    case 'DECREMENT': {
      return {
        count: state.count - 1,
      }
    }
    default:
      return state
  }
}
复制代码


// info.js
let initState = {
  info: {
    name: 'johan',
    description: '前端之虎',
  },
}
export default (state, action) => {
  if (!state) {
    state = initState
  }
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name,
      }
    case 'SET_DESCRIPTION':
      return {
        ...state,
        description: action.description,
      }
    default:
      return state
  }
}
复制代码


改造 createStore


export const createStore = (reducer, initState, enhancer) => {
    if (typeof initState === 'function') {
        enhancer = initState;
        initState = undefined
    }
    ...
    const getState = () => {
        return state
    }
  // 用一个不匹配任何动作来初始化store
    dispatch({ type: Symbol() })
    return {
        getState,
        dispatch,
        subscribe
    }
}
复制代码


主文件中


import { createStore, applyMiddleware, combinReducers } from './redux/index.js'
import {
  loggerMiddleware,
  exceptionMiddleware,
  timeMiddleware,
} from './middleware.js'
import { counterReducer, infoReducer } from './reducer/index.js'
const reducer = combinReducers({
  counter: counterReducer,
  info: infoReducer,
})
const store = createStore(
  reducer,
  applyMiddleware(loggerMiddleware, exceptionMiddleware, timeMiddleware),
)
console.dir(store.getState())
复制代码


到此为止,我们已经实现了一个七七八八的 redux 了


完整体的 Redux


退订


const subscribe = (fn) => {
  listeners.push(fn)
  return () => {
    const index = listeners.indexOf(listener)
    listeners.splice(index, 1)
  }
}
复制代码


中间件拿到的 store


现在的中间件能拿到完整的 store,他甚至可以修改我们的 subscribe 方法。按照最小开放策略,我们只用给 getState 即可,修改下 applyMiddleware 中给中间件传的 store


// const chain = middlewares.map(middleware => middleware(store))
const simpleStore = { getState: store.getState }
const chain = middlewares.map((middleware) => middleware(simpleStore))
复制代码


compose


在我们的 applyMiddleware 中,把 [A, B, C] 转换成 A(B(C(next))),效果是:


const chain = [A, B, C]
let dispatch = store.dispatch
chain.reverse().map((middleware) => {
  dispatch = middleware(dispatch)
})
复制代码


Redux 提供了一个 compose ,如下


const compose = (...funcs) => {
  if (funcs.length === 0) {
    return (args) => args
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码


2 行代码 replaceReducer


替换当前的 reudcer ,使用场景:


  • 代码分割


  • 动态加载


  • 实时 reloading 机制


const replaceReducer = (nextReducer) => {
  reducer = nextReducer
  // 刷新一次,广播 reducer 已经替换,也同样把默认值换成新的 reducer
  dispatch({ type: Symbol() })
}
复制代码


bindActionCreators


bindActionCreators 是做什么的,他通过闭包,把 dispatch 和 actionCreator 隐藏起来,让其他地方感知不到 redux 的存在。一般与 react-redux 的 connect 结合


这里直接贴源码实现:


const bindActionCreator = (actionCreator, dispatch) => {
  return function () {
    return dispatch(actionCreator.apply(this, arguments))
  }
}
export const bindActionCreators = (actionCreators, dispatch) => {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error()
  }
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
复制代码


以上,我们就已经完成了 Redux 中所有的代码。大体上这里 100 多行的代码就是 Redux 的全部,真 Redux 无非是加了些注释和参数校验


总结


我们把与 Redux 相关的名词列出来,梳理它是做什么的


  • createStore


  • 创建 store 对象,包含 getState、dispatch、subscribe、replaceReducer


  • reducer


  • 纯函数,接受旧的 state、action,生成新的 state


  • action


  • 动作,是一个对象,必须包括 type 字段,表示 view 发出通知告诉 store 要改变


  • dispatch


  • 派发,触发 action ,生成新的 state。是 view 发出 action 的唯一方法


  • subscribe


  • 订阅,只有订阅了,当派发时,会执行订阅函数


  • combineReducers


  • 合并 reducer 成一个 reducer


  • replaceReudcer


  • 代替 reducer 的函数


  • middleware


  • 中间件,扩展 dispatch 函数


砖家曾经画过一张关于 Redux 的流程图


image.png


换种思考方式理解



我们说过, Redux 只是一个状态管理库,它是由数据来驱动,发起 action,会引发 reducer 的数据更新,从而更新到最新的 store


与 React 结合


拿着刚做好的 Redux,放到 React 中,试试什么叫 Redux + React 集合,注意,这里我们先不使用 React-Redux,单拿这两个结合


先创建项目


npx create-react-app demo-5-react
复制代码


引入手写的 redux 库


App.js 中引入 createStore,并写好初始数据和 reducer,在 useEffect 中监听数据,监听好之后当发起一个 action 时,数据就会改变,看代码:


import React, { useEffect, useState } from 'react'
import { createStore } from './redux'
import './App.css'
const initState = {
  count: 0,
}
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      return state
  }
}
const store = createStore(reducer, initState)
function App() {
  const [count, setCount] = useState(store.getState().count)
  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setCount(store.getState().count)
    })
    return () => {
      if (unsubscribe) {
        unsubscribe()
      }
    }
  }, [])
  const onHandle = () => {
    store.dispatch({
      type: 'INCREMENT',
    })
    console.log('store', store.getState().count)
  }
  return (
    <div className="App">
      <div>{count}</div>
      <button onClick={onHandle}>add</button>
    </div>
  )
}
export default App
复制代码


点击 button 后,数据跟着改变


image.png


PS:虽然我们可以用这种方式订阅 store 和改变数据,但是订阅的代码重复过多,我们可以用高阶组件将他提取出去。这也是 React-Redux 所做的事情


与原生 JS+HTML 结合


我们说过,Redux 是个独立于 Redux 的存在,它不仅可在 Redux 充当数据管理器,还可以在原生 JS + HTML 中充当起职位


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="container">
      <div id="count">1</div>
      <button id="btn">add</button>
    </div>
    <script type="module">
      import { createStore } from './redux/index.js'
      const initState = {
        count: 0,
      }
      const reducer = (state, action) => {
        switch (action.type) {
          case 'INCREMENT':
            return {
              ...state,
              count: state.count + 1,
            }
          case 'DECREMENT':
            return {
              ...state,
              count: state.count - 1,
            }
          default:
            return state
        }
      }
      const store = createStore(reducer, initState)
      let count = document.getElementById('count')
      let add = document.getElementById('btn')
      add.onclick = function () {
        store.dispatch({
          type: 'INCREMENT',
        })
      }
      // 渲染视图
      function render() {
        count.innerHTML = store.getState().count
      }
      render()
      // 监听数据
      store.subscribe(() => {
        let state = store.getState()
        console.log('state', state)
        render()
      })
    </script>
  </body>
</html>
复制代码


效果如下:


image.png


状态生态



我们从 Flux 说到 Redux,再从 Redux 说了各种中间件,其中 React-saga 就是为解决异步行为而生的中间件,它主要采用 Generator(生成器)概念,比起 React-thunk 和 React-promise,它没有像其他两者将异步行为放在 action creator 上,而是把所有的异步操作看成“线程”,通过 action 触发它,当操作完成后再次发出 action 作为输出


function* helloWorldGenerator() {
  yield 'hello'
  yield 'world'
  yield 'ending'
}
const helloWorld = helloWorldGenerator()
hewlloWorld.next() // { value: 'hello', done: false }
hewlloWorld.next() // { value: 'world', done: false }
hewlloWorld.next() // { value: 'ending', done: true }
hewlloWorld.next() // { value: undefined, done: true }
复制代码


简单来说:遇到 yield 表达式,就暂停执行后面的操作,并将紧跟 yield 后面的那个表达式的值,作为返回值 value,等着下一个调用 next 方法,再继续往下执行


Dva


Dva 是什么?


官网:Dva 首先是一个基于 Redux + Redux-saga 的数据流方案。为了简化开发体验,Dva 还额外内置了 react-router 和 fetch,所以可以理解为一个轻量级的应用框架


简单来说,它是整合了现在最流行的数据流方案,即一个 React 技术栈:


dva = React-Router + Redux + Redux-saga + React-Redux


它的数据流图为:


image.png


view dispatch 一个动作,改变 state(即 store),state 与 view 绑定,响应 view

其他不表,可去 Dva 官网查看,这里讲讲 Model ,它包含了 5 个属性


  • namespace


  • model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间


  • state


  • 初始值


  • reducers


  • 纯函数,以 key/value 格式定义 reducer。用于处理同步擦做,唯一可以修改 state 的地方,由 action 触发


  • 格式为:(state, action) => newState[(state, action) => newState, enhancer]


  • effects


  • 处理异步操作和业务逻辑,以 key/value 格式定义 effect


  • 不直接修改 state。由 action 触发


  • call:执行异步操作


  • put:发出一个 Action,类似于 dispatch


  • subscriptions


  • 订阅


  • app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 链接、 keyboard 输入、history 路由变化、geolocation 变化等等


Mobx


View 通过订阅也好,监听也好,不同的框架有不同的技术,总之 store 变化, view 也跟着变化


Mobx 使用的是响应式数据流方案。后续会单独写一篇,此篇太长,先不写


补充:单向数据流



先介绍 React 中数据传递,即通信问题


  • 向子组件发消息


  • 向父组件发消息


  • 向其他组件发消息


React 只提供了一种通信方式:传参。


即父传值给子,子不能修改父传的数据,props 具有不可修改性。子组件想把数据传给父组件怎么办?通过 props 中的事件来传值通知父组件


相关文章
|
5天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
411 125
|
8天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
702 5
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
5天前
|
缓存 人工智能 运维
阿里云618百炼大模型Qwen3.7-Max功能、免费试用、订阅计费、配置接入详解
Qwen3.7-MAX是阿里云百炼平台推出的通义千问3.7系列旗舰大语言模型,专为智能体时代复杂任务打造,依托阿里云全域算力与自研技术,在逻辑推理、长文本处理、代码工程、长周期自主执行等领域达到行业顶尖水平。2026年618期间,该模型推出多重免费试用权益、按量计费5折、订阅套餐优惠等专属福利,覆盖个人开发者、团队与企业全场景需求,以下从核心功能、免费试用、订阅计费、配置接入四方面展开详细解析。
409 123
|
3天前
|
人工智能 自然语言处理 API
阿里云Token Plan团队版解析:功能、三档套餐与省钱订阅指南
阿里云百炼平台推出的Token Plan团队版,是面向企业与团队的AI大模型订阅服务,以Credits为统一计量单位,整合文本与图像生成模型,提供团队管理、数据安全、多工具兼容等核心能力,解决团队零散订阅AI服务的管理混乱、成本失控、数据安全等痛点。本文将从核心定位、套餐详情、计费规则、团队管理、工具兼容、便宜订阅技巧等方面,全面解析Token Plan团队版,帮助企业与团队高效、低成本地使用AI服务。
304 108
|
4天前
|
存储 人工智能 数据可视化
别再手动复制 Skill 了:多 Agent 时代的 Skill 管理方案
多 Agent 场景下 Skill 的统一管理与同步。
249 125
|
18天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
12天前
|
缓存 人工智能 运维
GLM 5.2自托管全流程实战:硬件选型、vLLM/SGLang部署与成本盈亏测算
2026年智谱发布GLM 5.2超大混合专家模型,区别于以往仅开放API的闭源大模型,该模型权重以MIT开源协议对外发布,企业与开发者可完整下载、本地审计、私有化部署,实现数据不出环境、自定义微调、自主调度推理资源。GLM 5.2拥有753B总参数,原生支持百万级上下文窗口,在代码生成、长文档推理、数学逻辑等多项基准测试中对标国际顶尖商用模型,是首款可完整自托管的前沿代码向大模型。
915 0
|
13天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)