Redux: Egghead 上的 Redux 截屏视频系列

创建于 2015-11-24  ·  69评论  ·  资料来源: reduxjs/redux

如果你正在关注 Redux repo 但还没有深入研究,或者对它的一些基本方面感到困惑,你会很高兴得知 Egghead 刚刚发布了我的Redux 入门系列

它涵盖了与文档的“基础知识”部分相同的主题,但希望能更深入一点,并确保您真正了解 Redux 基础知识。

我计划在 Egghead 上创建更多关于 Redux 的内容——这一次,仅供订阅者使用。 它将以更高级的主题为特色。 如果您有特别的建议,请在这个帖子中告诉我!

feedback wanted

最有用的评论

@markerikson感谢您在这一系列问题上接受我。 冒着被认为是争论的风险,我_do_ 想稍微反驳一下这是“应用程序级别”与“UI 级别”的想法。

注意:为方便起见,我将使用thunk来表示异步中间件方法,无论它实际上是 redux-thunk 还是 redux-promise 或其他。

您的 UI 层只是将用户交互连接到处理程序。 当有人单击按钮时,您的 UI 会被操纵以调用 _some_ 处理程序。 通常,组件将这些处理程序作为道具接收——例如,可能是绑定的动作创建者。 UI 不知道调用处理程序时会发生什么——它只是调用它。

对于 UI 层来说,“处理程序”是调度一个函数(由中间件处理)还是执行一个异步调用并_然后_调度一个普通的动作——UI 是完全不可知的(或者至少,它_可以_不可知论者)

无论您是否使用 thunk,您的“应用程序”的很大一部分都在这些“处理程序”中。 在典型的 react/redux 应用程序中,这些“处理程序”通常是某种动作创建者。 您可以将所有异步内容编写为 thunk,然后分派它们。 或者,您可以将所有异步内容编写为接受dispatch作为参数的函数。 从组件的角度来看,它要么是someHandler(dispatch)要么是dispatch(someHandler()) ,或者对于从上层传递下来的绑定动作创建者,在这两种情况下都只是someHandler() 。 您可以构建一个bindActionCreators ,它将完全掩盖与 UI 层的这种差异。

如果你给我一个使用 redux-thunk 由动作创建者编写的 react/redux 应用程序,我可以完全换掉 redux-thunk 并使用非中间件方法,而无需_从根本上_更改任何 UI 层。 (注意:我在掩饰你在哪里/如何注入getState ,但我_相信_这是一个小细节)。

因此,我很难接受“内部”和“外部”之间的区别是“应用程序级别”或“UI 级别”。

我很欣赏这次讨论,我希望我没有遇到负面影响。

所有69条评论

:+1:

  1. 本地组件的状态与全局状态
  2. 由多个减速器处理的动作与一对一的动作减速器关系
  3. 处理动作链(尤其是异步的),当第二个动作应该在第一个动作完成后立即触发时。
  4. 防止不必要的重新更新(批处理操作、重新选择等)的优化技术。

:+1:

我刚刚观看了该系列的前 16 集,并认为它们是一个极好的资源,我们很可能会在我工作的公司用于内部培训。 然而,你只使用普通对象和深度冻结作为你的状态树,并且从不使用或引用像immutable-js这样的

很高兴知道您对此的看法。

将 Immutable 混在一起会使初学者感到困惑,他们需要学习区分 Redux 和 Immutable API,可能会认为 Immutable 是必需的,等等。不过,对于一些高级课程来说,这是一个好主意!

将 Immutable 混在一起会使初学者感到困惑,他们需要学习区分 Redux 和 Immutable API,可能会认为 Immutable 是必需的,等等。不过,对于一些高级课程来说,这是一个好主意!

好吧,有道理。 很高兴知道你在这个决定背后的想法:-)。

@smashercosmo说了什么:+1:

单元测试。 TDD。

@cateland说了什么:+1:

我必须说你的技能给我留下了深刻的印象,能够以这种方式创建具有大量文档和简明教程的优秀 js 库。

看完所有视频后,我收到了一堆关于我将通过邮件发送的特定视频的反馈。 但结论是,这不仅仅是对 Redux 的介绍。 您涵盖代码实践和组件架构等。 这真的非常棒,而且非常有教育意义。 我认为这些视频可以分为三组。 Redux 基础,redux 幕后,redux 和 react。

对于即将发布的视频,我希望看到更少的反应特定概念和更多关于异步操作和测试的内容。

做的特别好,继续加油! :)

在视频 21 中,您注意到 TodoApp 组件不再需要是一个类,它可以是一个函数。 如果您能解释一下您是如何实现这一认识的,那就太好了——为什么这是一个合适的候选功能组件,这有什么好处?

这太好了。

@smashercosmo列表中的第 3 项也是我想知道的。

@wpannell说了什么! 单元测试/TDD!

还希望看到有关高级文档中涵盖的主题的视频。

关于单元测试,您对什么特别感兴趣?
第 5、11、12 课给出了如何对 reducer 进行单元测试的想法。

...这是个好问题。 当他们是摩卡测试时,这个过程会发生很大变化吗?

并不真地。 但我想这是一系列高级课程的好主题。 单元测试化简器、动作创建器、组件等。

首先,很棒的视频。 已经了解 redux 但令人耳目一新。
如果您正在创建更高级的视频,我可以建议异步操作/中间件。
不要认为单元测试真的需要任何覆盖。 你可以只调用你的 reducer 函数并断言它们吗?

是的,我想除了在 mocha 测试中包装expect之外,它真的没有什么其他的。 :竖起大拇指:

谢天谢地,一切都那么简单!

对象 ID 哈希的不变性(例如[post._id]: {...post} )。

我发现自己过于依赖reduce函数来获取 API 实体数组并用它生成 ID 哈希。 我知道normalizr会处理其中的一些问题,但我想要类似于 EggheadIO 视频的视频,您可以将我们从 A 点带到 B 点。它不仅仅是基于 Redux 的东西,而是紧密交织在一起的。

@raquelxmoss

在视频 21 中,您注意到 TodoApp 组件不再需要是一个类,它可以是一个函数。 如果您能解释一下您是如何实现这一认识的,那就太好了——为什么这是一个合适的候选功能组件,这有什么好处?

这实际上是我的一些草率的课程计划。 直到为时已晚,我才意识到我可以从一开始就让它成为一个功能组件。 使用函数而不是类的好处是简单,所以你可以随时这样做。 基本上,如果您不需要像componentDidMount或状态这样的生命周期钩子,请使用功能组件。

我会找到一个关于我们如何将这些代码片段拆分为实际目录结构的课程,这会有所帮助。 我知道只要一切都在那里,它可能会起作用,但可能会推荐命名文件夹(组件/容器/存储/reducer等)的约定,什么样的文件进入哪个文件夹等等会很好。

我也想要先进的减速机成分。

重用复杂的组件(由一个或多个 reducer、一组动作、动作创建者组成,可能访问一些服务器端 API、几个 React 组件——比如 redux-form,但更特定于实际应用程序)。 这还包括组织模块化目录结构。

全程看完了,不错! 我很欣赏使用 ES6/7 语法(类似Object.assign )、React 0.14 函数组件并避免不可变事物。

也许是解释推荐代码架构的视频。

并更新文档以使用 ES6/7 语法? (在那个方向欢迎公关吗?)

单元测试,创建 API 中间件,使用 Redux 进行 OAuth/某种类型的用户身份验证,使用不可变和 redux(如何设置它,最佳实践等)

本系列很好地介绍了 Redux、单原子状态和相关哲学。 我认为你在遵循核心原则同时避免诱发认知超载方面做得很好。 您工作的环境也很容易复制,这使得动手部分更加平易近人。

@gaearon您如何看待从一开始就将示例视频中的动作结构{ type: string, payload: Object } ? 我说的是计数器列表示例,其中有效负载被放置在操作对象本身上; { type: string, index: number } 。 这对我来说似乎是一种反模式。

您如何看待从一开始就将示例视频中的操作结构化为想要的标准 { type: string, payload: Object }? 我说的是计数器列表示例,其中有效负载被放置在操作对象本身上; {类型:字符串,索引:数字}。 这对我来说似乎是一种反模式。

无论如何,它都不是反模式。 这是一个正常的动作对象。 FSA很好,但它是一个_约定_。 Redux 本身没有任何东西依赖或受益于这个约定,所以我们不想强制执行它。

人们过去常常在原始 Flux 文档中思考关于payloadsource各种神奇事物。 他们盲目地复制了这些东西,却不了解它们为什么存在,并仔细评估它们是否甚至需要它们。 后来他们抱怨 Flux 复杂而冗长,实际上在许多情况下他们自己复制了冗长(但非必要)的部分。

在这些课程中,我只教授 Redux 的基本部分。 请注意我没有引入常量——因为人们过于关注它们,而忽略了它们并不重要。 在字符串中打了几个错字之后,您更有可能了解常量的好处,而不是我从一开始就将它们放入教程视频中。 我认为 FSA 等其他公约也是如此——无论如何,如果你觉得方便就使用它,但如果课程不需要它,我不会宣扬它。

@gaearon好的,那我和你在一起,希望那些习惯了简单的非结构化方法的人不会因为不应用任何约定而使更大的应用程序无法管理。

@sompylasar

我认为示例显示约定比视频教程更好。 示例是“我将如何围绕这些原则构建一些代码”的固执己见,视频是关于这些原则本身的。 这就是为什么您会在不同的示例中看到不同的约定,并且在开始一个项目之前,大多数人会查看不同的示例以了解不同的方法。

此外,不遵循 FSA 绝对没有什么非结构化的。 { type: 'STUFF', id: 1 }本质上并不比{ type: 'STUFF', payload: { id: 1 } } 。 这只是品味和(有时)工具约定的问题。 减少操作对象payload并不会使它们更难使用。

Egghead 上即将推出一些 redux 单元测试课程😄

我们推迟了一段时间的任何 Redux 课程,以便让 Dan 有先见之明。 完全值得等待,然后是一些。

“使用惯用的 Redux 构建应用程序”将是一门精彩的高级课程 👍

@joelhooks这两个听起来都很棒!

我希望看到您使用 webpack 和热重载而不是使用 jsbin 来设置项目。 把它带入现实世界。 我知道它不是特定于 redux 的,但我认为它很合适,你是教这个的合适人选:)

@kevinrenskers它不是一个视频系列,但如果你觉得就像解剖的东西,一些可以参考一下真的很好的例子boilerplates!

那些要求 Dan 的示例和 Webpack 配置的项目结构的人。

请查看https://github.com/urbanvikingr/todo。

我已承诺使用 React doc 更新 Redux,以与视频中 Dan 的代码保持一致。 它将在接下来的两周内完成 - 我的假期项目 :) - 请继续关注。

我对 Egghead.io 视频的愿望清单:
使用 Jasmine 进行动作/减速器测试
深入了解中间件(thunk / promises)

截屏视频是对 Redux 的非常有用的介绍。 我想听到的是 Redux 方式来进行路由和服务器端渲染

@grigio您可能对有关路由的讨论https://github.com/rackt/redux/issues/1145感兴趣

@urbanvikingr谢谢,已订阅。 民意调查似乎已结束,但我会投票 #1168

这些课程实际上非常好,但令人分心的是,当您说“商店”时,它听起来像是“家务活”。

这里的每个人都注意到并被它分散了注意力。 他们在政治上太正确了,不能提出来。 所以,是的,我决定成为_那个_家伙,以防其他人永远不会:)

我最终会在说英语方面变得更好。 现在这是我能做的最好的;-)

@gaearon你在解释 Redux 方面做得非常好。 向您和您的开源成就致敬。

一组惊人的课程。 我特别喜欢您如何以“阅读源代码”的方式从头开始重新实现每个核心 Redux 功能。 借调其他对 Redux 教程和所有文档的清晰程度印象深刻的人。 到目前为止,作为一个追赶两年前端进步的人,这些概念很难让我想到,但文档在这方面提供了惊人的帮助。 继续加油,谢谢!

(也不要听@jugimaster 的话,不是每个人都被你的口音分心,并且“政治上太正确以至于不能提起它”,甚至不在乎你有口音。)

@ianstormtaylor
顺便说一句,这不是口音:)

我也没有“关心”,但我确实分心了!

但另一方面,作为一个学过六门外语的人,我_have_总是关心说正确的语言,为此,我个人感谢有人指出我的错误。

@gaearon

绝对喜欢你的课程。 干得好! 这很有帮助。

我在最近的 Egghead 课程中添加了三个 Redux 测试视频课程:
https://egghead.io/series/react-testing-cookbook

我希望他们能对你所做的所有惊人的工作表示赞赏!

扎实的课程!

不仅提高了 Redux 知识,而且全面提高了现代实践知识! 继续做这么好的事情:+1: :tada:

嘿,刚刚意识到视频中没有代码的链接/参考。 也许很明显,也许足够简单的用户可以复制视频; 但我认为很多人都可以从视频中包含确切代码的存储库中受益——为什么不呢?

Egghead 订阅者可以使用每节课的代码片段。 :-)
视频是免费的,但平台必须赚钱,所以它可以投资更多的课程,向讲师发送设备,托管视频,改进网站等等。

也就是说,我们有一个与课程非常匹配的examples/todos文件夹。

......酷,我想念它吗? 正在寻找链接...

@gaearon道歉,刚刚重温了视频。 代码就在那里 :)... 观看了视频,购买了会员资格,观看了其他人...刚刚回到实际登录的 redux 视频。干杯。

顺便说一句,一些人抱怨成绩单不准确。
请发送 PR 来修复它们: https :

@gaearon我决定使用redux,然后在egghead上找到了redux vids。 这些视频真的帮助我开始学习 redux。 展望未来,很高兴看到更多现实世界的例子。

所以我对 redux 视频培训的建议是高级组合,更深入地研究重构,以及如何使用重新选择。 您似乎对何时重构有直觉。 因此,由于函数式编程与 redux 密切相关,因此从您那里获得一些关于何时重构以及如何识别一个能很好地完成一件事的函数的提示将非常有用。

在我正在构建的应用程序中,我有几个大型数据集合,我需要将它们放入表中并执行诸如对数据进行排序和分页之类的操作。 我很难决定何时使用选择器以及何时使用创建操作。 目前我有 USERS_SORT_TABLE 动作和 SORT_TABLE 动作,因为我让用户存储“继承”表中的某些状态。 我这样做是因为我不希望待办事项商店上的 SORT_TABLE 操作被用户商店排序。

我知道我的解决方案不是 DRY,但我不确定如何正确执行。 就目前而言,我将不得不为要填充表的每个商店创建一个“SOMETHING”_SORT_TABLE 操作,我知道这是错误的,但我不知道正确的方法。 另一个副作用是我的动作名称越来越大,因为我必须通过在动作前面加上它们的名称来分隔不同的商店。 这不可能。

这是一些示例代码:

/* actions.js */
// ...
export const USER_MOVE_COLUMN = 'USER_MOVE_COLUMN'

export function userMoveColumn (columnIndex, moveToIndex) {
  return {
    type: USER_MOVE_COLUMN,
    columnIndex,
    moveToIndex
  }
}

export const DATA_TABLE_MOVE_COLUMN = 'DATA_TABLE_MOVE_COLUMN'
// ...

/* reducers.js */
// ...
export default function user (state=userInitialState, action) {
  switch (action.type) {
    // ...
    case USER_MOVE_COLUMN:
      return dataTable(state, assign(
        action,
        {type: DATA_TABLE_MOVE_COLUMN}
      ))
    // ...
    default:
      return state
  }
}
// ...
export default function dataTable (
  state=dataTableInitialState,
  action,
  key='dataTable')
{
  switch (action.type) {
    // ...
    case DATA_TABLE_MOVE_COLUMN:
      return {
        ...state,
        [key]: {
          ...state[key],
          columns: move(
            state[key].columns, action.columnIndex, action.moveToIndex
          )
        }
      }
    // ...
    default:
      return state
  }
}

所以你可以看到我在表和我不应该拥有的“模型”存储之间创建了一个依赖关系(模型存储必须使用它的对象数组的集合键)。 并且 dataTable 操纵“父”减速器的状态,这是不应该的。 我突然想到我需要在那里使用选择器,但在我写这篇文章的时候,我试图避免复制一个大型商店,只是为了改变 UI 中可见的内容。

所以目前我正在努力学习重新选择来解决其中一些问题和重构技术来解决其他一些问题。 第一个 Redux 课程就足以让我变得危险。 现在我想学习如何正确地做到这一点。 :)

我希望这是有帮助的,而不是太冗长。 试图给出清晰、诚实的反馈。

对于任何可能帮助我的好心人,我已经从 Dan 的另一条评论中找到了/examples/real-world/reducers ,我目前正在修改我上面概述的一些问题。 如果我找到了解决方案,不想让您浪费时间尝试提供帮助。

谢谢你的提醒 :)

在我的项目中集成 redux-devtools 对我来说是一个巨大的痛苦.. 我会(并且仍然会)欣赏一个描述现有内容以及如何/何时使用它的蛋头系列。 我读了你描述什么时候使用什么的 PR,但是我很困惑,那里有很多东西 hmr,transform 3,redux hot reloader 与 react hot reloader 不同等等..

对于在上面列出的一些问题上苦苦挣扎的其他人,我创建了一个项目,允许您删除 Redux 中的大部分(如果不是全部)样板以及命名空间操作。 看这里

@granteagon这对减少者不是动作创建者吗? Redux 的核心设计原则之一是_reducers 应该与actions 解耦_。 任何 reducer 都可以监听任何动作。 它们之间没有 1:1 的映射关系。 否则,状态树的不同部分很难独立地改变它们的状态以响应相同的动作。

我注意到大多数人在创建 action/reducer 之上创建包装器或抽象倾向于假设它们总是直接耦合在一起。 诚然,在我的应用程序中,到目前为止,我的 _大多数_ 操作都有一个对应的处理逻辑块,但肯定有几个需要更新树的多个部分。

@gaearon @markerikson它确实将减速器与动作创建者结合起来。 但是,在 90% 的情况下,这是可以的,甚至是期望的。 另外 10% 的时间你仍然可以使用手动编码的方法。 我会考虑你所说的,并考虑到未来的发展。

@granteagon @gaearon

使用了类似于 reduxr 的本地抽象,我认为这里没有任何额外的耦合。 没有什么会强制在 action 和 reducer 之间进行 1:1 映射。 您仍然可以在两个不同的切片中使用两个减速器来监听相同的操作:

const counterReducersA = {
  // this counter increments by 1 each time
  increment: (state, action) => state + 1
}

const counterReducersB = {
  // this counter increments by 2 each time
  increment: (state, action) => state + 2
}

const counterA = reduxr(counterReducersA, 0);
const counterB = reduxr(counterReducersB, 0);

const rootReducer = combineReducers({
  counterA: counterA.reducer,
  counterB: counterB.reducer
});

store.dispatch(counterA.action.increment());  // increments both counters

当然,如果你有多个“reducer”函数命名相同的东西(即响应相同的动作),他们都需要隐含地“预期”动作有效负载将处于某种形状 --- 这完全类似在 vanilla redux 中有两个减速器都处理相同的type常量 --- 两者都必须期望相同的动作形状。

也许我误解了你所说的耦合是什么意思, @gaearon

我认为在展示 thunk 实现之前展示一个没有中间件的异步流程可能会有所帮助。

有点相关,但是虽然文档非常有用,但事后看来,异步流页面上的这个声明确实让我偏离了轨道:“没有中间件,Redux 存储仅支持同步数据流。”

@battaile :那是因为它是真的 :) 如果没有中间件,任何异步都必须完全在 Redux 之外发生(因此,很可能在您的 UI 层中,例如 React 组件)。 任何时候你调用store.dispatch ,动作都会直接进入 reducer 函数,不要通过 Go,不要收取 200 美元,不要在 AJAX 调用过程中停下来。

存储增强器允许您将dispatch类的函数包装applyMiddleware在某些内容到达真实存储的dispatch函数之前提供了“中间件管道”的抽象. 这基本上提供了一个漏洞,您可以跳出并执行您想要的任何异步操作,_inside_ 标准 Redux 流程。

所以,没有中间件,你仍然可以完全做异步的事情......它必须完全独立于与 Redux 实际相关的任何事情。

那是因为这是真的:)

我没有说它是假的,我说它让我偏离了轨道:)

我只是想做类似以下的事情,这似乎暗示我做不到:

const mapDispatchToProps = (dispatch) => ({
  onclick(searchTerm) {
    dispatch(actions.requestOrders(searchTerm));

    return fetch('http://localhost:49984/Order/Search?search=' + searchTerm)
      .then(response => response.json()).then(response => {
        dispatch(actions.receiveOrders(searchTerm, response));
      })
      .catch((err) => {
        dispatch(actions.receiveOrdersError('An error occurred during search: ' + err.message));
      });
  },
});

我意识到这很容易变得丑陋,但从概念上讲,我认为它很有用。 或者至少在我的情况下是这样。

我同意“没有中间件,Redux store 只支持同步数据流”。 具有误导性。

从技术上讲,如果您想区分“内部”和“外部”,该陈述可能是正确的,但如果它使人们相信执行异步的唯一方法是添加中间件,也许我们可以改写它或详细说明它。

是的,不同之处在于异步行为在技术上更多地发生在组件级别,而不是dispatch “内部”。 一个微小的区别,但一个有效的区别。

我认为实际上没有人认为该声明在技术上不正确。

@markerikson只是好奇您是否有任何具体的例子来说明内部和外部之间的区别?

一个示例可能是,如果您希望链中的中间件_before_您的异步中间件能够看到您分派的 thunk(或 promise 等)。 我不确定中间件会_做什么_,但我想想要这样的东西是可行的。

嗯...不确定具体的“具体”示例。 总的来说,“outside”和“insie”之间的区别在于它是否发生在_before_你第一次调用dispatch ,还是_after_。 如果它是“外部”和“之前”,那么您所有的异步性和逻辑都更多地与视图层相关,无论是 React、Angular 还是其他。 如果它是“内部”和“之后”,那么您的异步性和逻辑在存储级别,并且_不_绑定到视图层。

这实际上是我今天早些时候在 Reddit 讨论中试图提出的大部分观点: https :

“我要发送哪些操作以及何时发送它们?”的问题。 是您业务逻辑的核心部分,另一半是“我如何更新我的状态以响应这些操作?”。 如果动作管理在 thunk 和 sagas 等中,那么该代码是否由 React 组件、Angular 控制器、jQuery 单击处理程序、Vue 组件实例或其他东西启动并不重要。 你的核心逻辑在 UI 层之外,而 UI 层实际上只是负责从存储中拉取它需要的数据,显示它,并将用户输入转换为应用程序逻辑函数调用。

所以,从这个意义上说,我想说“内部”与“外部”的问题确实很重要,因为这是您的逻辑是生活在应用程序级别还是 UI 级别之间的概念区别。

@markerikson感谢您在这一系列问题上接受我。 冒着被认为是争论的风险,我_do_ 想稍微反驳一下这是“应用程序级别”与“UI 级别”的想法。

注意:为方便起见,我将使用thunk来表示异步中间件方法,无论它实际上是 redux-thunk 还是 redux-promise 或其他。

您的 UI 层只是将用户交互连接到处理程序。 当有人单击按钮时,您的 UI 会被操纵以调用 _some_ 处理程序。 通常,组件将这些处理程序作为道具接收——例如,可能是绑定的动作创建者。 UI 不知道调用处理程序时会发生什么——它只是调用它。

对于 UI 层来说,“处理程序”是调度一个函数(由中间件处理)还是执行一个异步调用并_然后_调度一个普通的动作——UI 是完全不可知的(或者至少,它_可以_不可知论者)

无论您是否使用 thunk,您的“应用程序”的很大一部分都在这些“处理程序”中。 在典型的 react/redux 应用程序中,这些“处理程序”通常是某种动作创建者。 您可以将所有异步内容编写为 thunk,然后分派它们。 或者,您可以将所有异步内容编写为接受dispatch作为参数的函数。 从组件的角度来看,它要么是someHandler(dispatch)要么是dispatch(someHandler()) ,或者对于从上层传递下来的绑定动作创建者,在这两种情况下都只是someHandler() 。 您可以构建一个bindActionCreators ,它将完全掩盖与 UI 层的这种差异。

如果你给我一个使用 redux-thunk 由动作创建者编写的 react/redux 应用程序,我可以完全换掉 redux-thunk 并使用非中间件方法,而无需_从根本上_更改任何 UI 层。 (注意:我在掩饰你在哪里/如何注入getState ,但我_相信_这是一个小细节)。

因此,我很难接受“内部”和“外部”之间的区别是“应用程序级别”或“UI 级别”。

我很欣赏这次讨论,我希望我没有遇到负面影响。

这门课很棒。 关闭它,以便人们可以将他们的评论指向课程的社区笔记仓库: https :

另外,一定要看看丹放在一起的下一个系列! https://egghead.io/courses/building-react-applications-with-idiomatic-redux

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

ms88privat picture ms88privat  ·  3评论

timdorr picture timdorr  ·  3评论

vraa picture vraa  ·  3评论

dmitry-zaets picture dmitry-zaets  ·  3评论

cloudfroster picture cloudfroster  ·  3评论