React教程:组件,Hooks和性能

image

翻译:疯狂的技术宅
原文:https://www.toptal.com/react/react-tutorial-pt2

正如 我们的React教程第一部分 【点击直达】中所指出的,开始使用 React 相对容易。首先使用 Create React App(CRA)初始化一个新项目,然后开始开发。不过遗憾的是,随着时间的推移,代码可能会变得难以维护,特别是在你不熟悉 React 的情况下。组件有可能会变大,或者你可能最终得到一堆不是组件的组件,最终你可能会到处编写重复的代码。

这时候你就应该试着开始真正的 React 之旅了 —— Think in React。

每当开发一个新的程序时,你需要为其做好在以后转换为 React 应用的新设计,首先试着确定设计草图中的组件,如何分离它们以使其更易于管理,以及哪些元素是重复的(或他们的行为)。尽量避免添加可能“将来有用”的代码 —— 虽然这很诱人,但可能未来永远也不会到来,你将留下一堆具有大量可配置选项的多余通用功能/组件。

image.gif

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">React 组件</figcaption>

此外,如果一个组件大于 2 到 3 个窗口的高度,也许值得分离(如果可能的话) —— 以后更容易阅读。

React 中的受控组件与非受控组件

在大多数应用中,需要输入和与用户进行某种形式的交互,允许他们输入内容、上传文件、选择字段等。 React 用两种不同的方式处理用户交互 —— 受控非受控组件。

顾名思义,受控组件的值由 React 控制,能为与用户交互的元素提供值,而不受控制的元素不获取值属性。多亏了这一点,我们才能把 React 状态作为单一的事实来源,因此我们在屏幕上看到的与当前拥有的状态是一致的。开发人员需要传递一个函数,该函数用来响应用户与表单的交互,这将会改变它的状态。

 1class ControlledInput extends React.Component { 2 state = { 3   value: "" 4 }; 5 6 onChange = (e) => this.setState({ value: e.target.value }); 7 8 render() { 9   return (10     <input value={this.state.value} onChange={this.onChange}/>11   );12 }13}14

在 React 的非受控组件中,我们不关心值的变化情况,如果想要知道其确切的值,只需通过 ref 访问它。

 1class UncontrolledInput extends React.Component { 2 input = React.createRef(); 3 4 getValue = () => { 5   console.log(this.input.current.value); 6 }; 7 8 render() { 9   return (10     <input ref={this.input}/>11   );12 }13}14

那么应该怎么选择呢?在大数情况下用受控组件是可行的,不过也有一些例外。例如使用非受控制组件的一种情况是 file 类型输入,因为它的值是只读的,不能在编码中去设置(需要用户交互)。另外我发现受控组件更容易理解和于使用。对受控组件的验证是基于重新渲染的,状态可以更改,并且可以很轻松的显示输入中存在的问题(例如格式错误或者输入为空)。

Refs

在前面我们提到过 refs,这是一个特殊功能,可以在类组件中使用,直到 16.8 中出现了 hooks。

refs 可以通过引用让开发人员访问 React 组件或DOM元素(取决于我们附加 ref 的类型)。最好仅在必须的场景中使用它们,因为它们会使代码难以阅读,并打破从上到下的数据流。然而,有些情况下它们是必要的,特别是在DOM元素上(例如:用编码方式改变焦点)。附加到 React 组件元素时,你可以自由使用所引用的组件中的方法。不过还是应该避免这种做法,因为有更好的方法来处理它(例如,提升状态并将功能移动到父组件)。

refs 还可以做到:

  • 使用字符串字面量(历史遗留的,应该避免),

  • 使用在 ref 属性中设置的回调函数,

  • 通过创建 ref 作为 React.createRef() ,并将其绑定到类属性,并通过它去访问(请注意,在 componentDidMount 生命周期中将提供引用)。

没有传递引用的一种情况是当在组件上使用高阶组件时 —— 原因是可以理解的,因为 ref 不是 prop(类似于 key)所以它没有被传递下来,并且它将引用 HOC 而不是被它包裹的组件。在这种情况下,我们可以使用React.forwardRef,它把 props 和 ref 作为参数,然后可以将其分配给 prop 并传递给我们想要访问的组件。

 1function withNewReference(Component) { 2 class Hoc extends React.Component { 3   render() { 4     const {forwardedRef, ...props} = this.props; 5 6     return <Component ref={forwardedRef} {...props}/>; 7   } 8 } 910 return React.forwardRef((props, ref) => {11   return <Hoc {...props} forwardedRef={ref} />;12 });13}

错误边界

事情越复杂,出现问题的概率就越高。这就是为什么 React 中会有错误边界。那他们是怎么工作的呢?

如果出现问题并且没有错误边界作为其父级,则会导致整个React 应用失败。不显示信息比误导用户并显示错误信息要好,但这并不意味着你应该放任整个应用崩溃并显示白屏。通过错误边界,可以得到更多的灵活性。你可以在整个应用程序中使用并显示一个错误消息,或者在某些小部件中使用它但是不显示,或者显示少量信息来代替这些小部件。

请记住,它仅涉及声明性代码的问题,而不是你为了处理某些事件或者调用而编写的命令式代码。对于这些情况,你仍应使用常规的 try/catch 方法。

在错误边界也可以将信息发送到你使用的 Error Logger (在 componentDidCatch 生命周期方法中)。

 1class ErrorBoundary extends React.Component { 2  state = { hasError: false }; 3 4  static getDerivedStateFromError(error) { 5    return { hasError: true }; 6  } 7 8  componentDidCatch(error, info) { 9    logToErrorLogger(error, info);10  }1112  render() {13    if (this.state.hasError) {14      return <div>Help, something went wrong.</div>;15    }1617    return this.props.children; 18  }19}

高阶组件

高阶组件(HOC)经常在 React 中被提及,这是一种非常流行的模式,你可能会用到它(或者已经在用了)。如果你熟悉 HOC,可能已经在很多库中看到过 withNavigation,connect,withRouter

HOC 只是一种把组件作为参数的函数,并且与没有 HOC 包装器的组件相比,能够返回具有扩展功能的新组件。多亏了这一点,你可以实现一些易于扩展的功能,以此增强自己的组件(例如:访问导航)。 HOC 也有一些其它形式的调用方式,这取决于我们当前拥有什么,唯一的参数必须要传入一个组件,但它也可以接受额外的参数 —— 一些选项,或者像在 connect 中一样,首先使用configurations调用一个函数,该函数稍后返回一个带参组件,并返回 HOC 。

以下是一些你应该做的和要避免做的事情:

  • 为包装器 HOC 函数添加显示名称(这样你就能知道它到底是干什么用的,实际上是通过更改 HOC 组件显示名称来做到)。

  • 不要在渲染方法中使用HOC —— 你应该在其中使用增强组件,而不是在那里创建新的 HOC 组件,因为它一直在重新装载并丢失其当前状态。

  • 静态方法不会被自动复制,所以如果你想在新创建的 HOC 中使用一些静态方法,需要自己去复制它们。

  • 涉及到的 Refs 不会被传递,所以使用前面提到的 React.forwardRef 来解决这些问题。

 1export function importantHoc() { 2   return (Component) => class extends React.Component { 3       importantFunction = () => { 4           console.log("Very Important Function"); 5       }; 6 7       render() { 8           return ( 9               <Component10                   {...this.props}11                   importantFunction={this.importantFunction}12               />13           );14       }15   };16}17

样式

样式不一定与 React 本身有关,但出于各种原因还是值得一提的。

首先,常规 CSS/内联样式在这里能够正常应用,你只需在 className 属性中添加 CSS 中的类名,它就能正常工作。内联样式与常规 HTML 样式略有不同。样式属性也是使用驼峰命名法,因此 border-radius 会变成 borderRadius 。

React 似乎推广了一些不仅在 React 中变得普遍的解决方案,例如最近集成在 CRA 中的 CSS 模块,你可以在其中简单地导入 name.modules.css 并用其属性来调整组件的样式(某些IDE(例如WebStorm)也具有自动完成功能,能告诉你可用的名称。

在 React 中另一个流行的解决方案是 CSS-in-JS(例如,emotion 库)。再说一点,CSS 模块和 emotion(或者一般来说是CSS-in-JS)对 React 没有限制

React 中的 Hooks

自重写以来,**Hooks **很可能是 React 最受热切期待的补充。这个产品是否能不负众望?从我的角度来看,是的,因为它确实是一个很棒的功能。它们本质上是带来了新的体验,例如:

  • 允许删除许多 class 组件,这些组件我们仅仅是使用而不归我们拥有,例如本地状态或 ref,所以组件的代码看上去更容易阅读。

  • 可以让你用更少的代码来获得相同的效果。

  • 使函数更容易理解和测试,例如:用 react-testing-library【https://github.com/kentcdodds/react-testing-library】。

  • 也可以携带参数,一个 hook 返回的结果可以很容易地被另一个 hook 使用(例如,useEffect 中的 setStateuseState 使用)。

  • 比类更好地缩小方式,这对于 minifiers 来说往往更成问题。

  • 可能会删除 HOC 并在你的应用中渲染 props ,尽管 hook 被设计用于解决其他问题,但仍会引入新问题。

  • 能够被熟练的React开发人员定制

默认的 React hook 很少。其中三个基本的hook是 useStateuseEffectuseContext。还有一些其它的,例如 useRefuseMemo,不过现在我们把重点放在基础知识上。

先看一下 useState,让我们用它来创建一个简单的计数器的。它是如何工作的?基本上整个结构非常简单:

 1export function Counter() { 2 const [counter, setCounter] = React.useState(0); 3 4 return ( 5   <div> 6     {counter} 7     <button onClick={() => setCounter(counter + 1)}>+</button> 8   </div> 9 );10};

它用 initialState (值)调用,并返回一个带有两个元素的数组。由于数组解构分配,我们可以立即将变量分配给这些元素。第一个是更新后的最后一个状态,而另一个是我们将用于更新值的函数。看起来相当容易,不是吗?

此外,由于这些组件曾经被称为无状态功能组件,现在这种名称不再适用,因为它们可以具有如上所示的状态。所以叫类组件函数组件似乎更符合它们的实际操作,至少从16.8.0开始。

更新函数(在我们的例子中是setCounter)也可以用作一个函数,它将以前的值作为参数,格式如下:

1<button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button>2<button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>

与执行浅合并的this.setState 类组件不同,设置函数(在我们的例子中为 setCounter)会覆盖整个状态。

另外,initialState 也可以是一个函数,而不仅仅是一个普通的值。这有其自身的好处,因为该函数将会只在组件的初始渲染期间运行,之后将不再被调用。

1const [counter, setCounter] = useState(() =>  calculateComplexInitialValue());

最后,如果我们要使用 setCounter 与在当前状态(counter)的同一时刻完全相同的值,那么组件 将不会 重新渲染。

另一方面,useEffect 为我们的功能组件添加副作用,无论是订阅、API调用、计时器、还是任何我们认为有用的东西。我们传给 useEffect 的任何函数都将在 render 之后运行,并且是在每次渲染之后执行,除非我们添加一个限制,把应该重新运行时需要更改的属性作为函数的第二个参数。如果我们只想在 mount 上运行它并在unmount 上清理,那么只需要在其中传递一个空数组。

 1const fetchApi = async () => { 2 const value = await fetch("https://jsonplaceholder.typicode.com/todos/1"); 3 console.log(await value.json()); 4}; 5 6export function Counter() { 7 const [counter, setCounter] = useState(0); 8 useEffect(() => { 9   fetchApi();10 }, []);111213 return (14   <div>15     {counter}16     <button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button>17     <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>18   </div>19 );20};

由于把空数组作为第二个参数,所以上面的代码只运行一次。在这种情况下它类似于 componentDidMount,但稍后会触发它。如果你想在浏览器处理之前调用一个类似的 hook,可以用 useLayoutEffect,但这些更新将会被同步应用,这一点与 useEffect 不同。

useContext 似乎是最容易理解的,因为我们提供了想要访问的上下文(由 createContext 函数返回的对象提供),而它为我们提供了该上下文的值。

1const context = useContext(Context);

最后,要编写自己的hook,你可以像这样写:

 1function useWindowWidth() { 2 let [windowWidth, setWindowWidth] = useState(window.innerWidth); 3 4 function handleResize() { 5   setWindowWidth(window.innerWidth); 6 } 7 8 useEffect(() => { 9   window.addEventListener('resize', handleResize);10   return () => window.removeEventListener('resize', handleResize);11 }, []);1213 return windowWidth;14}

基本上,我们使用常规的 useState hook,我们将其指定为窗口宽度的初始值,然后在 useEffect 中添加一个监听器,它将在窗口调整大小时触发 handleResize。在组件被卸载后会我们会及时知道(查看 useEffect 中的返回值)。是不是很简单?

注意: use 在 hook 中很重要。之所以使用它,是因为它允许 React 检查你是否做了不好的事情,例如从常规JS函数调用hook。

类型检查

在支持 Flow 和 TypeScript 之前,React有自己的属性检查机制。

PropTypes 检查 React 组件接收的属性(props)是否与我们的内容一致。如果一致(例如:应该是对象而不是数组),将会在控制台中收到警告。请务必注意:PropTypes 仅在开发模式下进行检查,因为它们会影响性能并在控制台中显示上述警告。

从React 15.5开始,PropTypes 被放到了不同的包里,需要单独安装。它在名为 propTypes(surprise)的静态属性中对属性进行声明,可以把它与 defaultProps 结合使用,如果属性未定义就会使用它们(undefined是唯一的情况)。 DefaultProps 与 PropTypes 无关,不过它们可以解决由于 PropTypes 而可能出现的一些警告。

另外两个选择是 Flow 和 TypeScript,它们现在更受欢迎(特别是 TypeScript )。

  • TypeScript是 Microsoft 开发的 JavaScript 的类型超集,它可以在程序运行之前检查错误,并为开发工作提供卓越的自动完成功能。它还极大地改善了重构过程。由于受到 Microsoft 的支持,它有丰富的类型语言特征,也是一个相当安全的选择。

  • Flow与TypeScript不同,它不是一种语言,而是 JavaScript 的静态类型检查器,因此它更像是 JavaScript 中的工具而并非语言。 Flow 背后的整个思路与 TypeScript 完全相似。它允许你添加类型,以便在运行代码之前杜绝可能出现的错误。就像 TypeScript 一样,CRA(创建React App)从一开始就支持 Flow。

我发现 TypeScript 更快(几乎是即时的),特别是在自动完成中,Flow 似乎有点慢。值得注意的是,我自己用的 WebStorm 等 IDE 使用 CLI 与 Flow 集成。但是在文件中集成可选用法似乎更容易,只需要在文件开头添加 // @flow 就可进行类型检查。另外据我所知,似乎 TypeScript 最终赢得了与 Flow 的战斗 —— 它现在更受欢迎,并且一些最流行的库正在从 Flow 转向 TypeScript。

官方文档中还提到了更多的选择,例如 Reason(由Facebook开发并在React社区中获得普及),Kotlin(由JetBrains开发的语言)等等。

显然,对于前端开发人员来说,最简单的方法是使用 Flow 和 TypeScript,而不是切换到 Kotlin 或F#。但是,对于正在转型到前端的后端开发人员来说,这可能更容易入手。

生产模式和 React 性能

对于生产模式,你需要做的最基本和明显的改变是:把 DefinePlugin 切换到 “production”,并在Webpack的情况下添加UglifyJsPlugin。在使用 CRA 的情况下,它就像使用 npm run build(将运行react-scripts build)一样简单。请注意,Webpack 和 CRA 不是唯一的选项,因为你可以使用其他构建工具,如 Brunch。这通常包含在官方文档中,无论是官方的 React 文档还是特定工具的文档。要确保模式设置正确,你可以使用React Developer Tools【https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi】,它会告诉你正在用的那种构建(生产与开发)模式应该怎么配置。上述步骤会使你的应用在没有来自 React 的检查和警告的情况下运行,并且 bundle 本身也将被最小化。

你还可以为 React 应用做更多的事。你如何处理构建的 JS 文件?如果尺寸相对较小,你可以从 “bundle.js” 开始,或者做一些类似 “vendor + bundle” 或者 “vendor + 最小化需要部件 + 在需要时导入东西” 之类的处理。当你是处理一个非常大的应用时,不需要在一开始就导入所有内容。请注意,在主 bundle 中去 bundling 一些不会被使用的 JavaScript 代码只会增加 bundle 包的大小,并会使应用在启动时的加载速度变慢。

如果你计划冻结库的版本,并认为它们可能长时间内不会被更改,那么 Vendor bundles 可能很有用。此外,更大的文件更适合用 gzipping,因此从拆分获得的好处有时可能不值得。这取决于文件大小,有时你需要自己去尝试。

代码拆分

代码拆分的方式比这里给出的建议多得多,但让我们关注 CRA 和 React 本身可用的内容。基本上,为了将代码分成不同的块,可以使用 import(),这可以用 Webpack 支持( import本身是第3阶段的提案,所以它还不是语言标准的一部分)。每当 Webpack 看到 import 时,它就会知道需要在这个阶段开始拆分代码,并且不能将它包含在主包中(它在import中的代码)。

现在我们可以将它与 React.lazy() 连接起来,它需要 import() 一个文件路径,其中包含需要在那个地方渲染的组件。接下来,我们可以用 React.suspense(),它会在该位置显示不同的组件,一直到导入的组件全部加载完毕。有人可能会想,如果我要导入单个组件,是不是就不需要它了呢?

实际上并非如此,因为 React.lazy() 将显示我们 import() 的组件,但 import() 可能会获取比单个组件更大的块。例如这个组件可能包含其他库,或更多代码,所以不只是需要一个文件 —— 它可能是绑在一起的多个文件。最后,我们可以将所有这些包装在 ErrorBoundary(你可以在本文关于错误边界的那部分中找到代码) 如果某些内容因我们想要导入的组件而失败(例如出现网络错误),这将作为备用方案。

 1import ErrorBoundary from './ErrorBoundary'; 2 3const ComponentOne = React.lazy(() => import('./ComponentOne')); 4 5function MyComponent() { 6   return ( 7       <ErrorBoundary> 8           <React.Suspense fallback={<div>Loading...</div>}> 9               <ComponentOne/>10           </React.Suspense>11       </ErrorBoundary>12   );13}14

这是一个简单的例子,但显然你可以做得更多。你可以使用 importReact.lazy 进行动态路由划分(例如:管理员与常规用户)。请注意,React.lazy 仅支持默认导出,并且不支持服务器端呈现。

React 代码性能

关于性能,如果你的 React 应用运行缓慢,有两种工具可以帮助你找出问题。

第一个是 Chrome Performance Tab,它会告诉你每个组件会发生什么(例如,mount,update )。有了它你应该能够确定哪个组件可能会出现性能问题,然后进行优化。

另一个选择是 DevTools Profiler ,它在 React 16.5+ 中可用,并与 shouldComponentUpdate 配合(或PureComponent,在本教程的第一部分中解释),我们可以提高一些关键组件的性能。

显然,对网络进行基本优化是最佳的,例如对一些事件进行去抖动(例如,滚动),对动画保持谨慎(使用变换而不是通过改变高度并实现动画)等等。这些问题很容易被忽略,特别是如果你刚刚掌握了 React。

2019年及以后的 React 现状

如果要讨论 React 的未来,我个人不会太在意。从我的角度来看,React 在 2019 年及以后的地位很难被撼动。

React 拥有如此强大的地位,在一个大社区的支持下很难被废弃。 React社区非常棒,它总是产生新的创意,核心团队一直在不断努力改进 React,并添加新功能和修复旧问题。 React 也得到了一家大公司的支持,但许可证已经不是问题 —— 它现在使用 MIT license。

是的,有一些事情有望改变或改进;例如,使 React 稍微小一些(提到的一个措施是删除合成事件)或将 className 重命名为 class。当然,即使这些看似微小的变化也可能导致诸如影响浏览器兼容性等问题。就个人而言,我也想知道当 WebComponent 获得更多人气时会发生什么,因为它可能会增加一些 React 经常用到的东西。我不相信他们会成为一个彻头彻尾的替代者,但我相信他们可以很好地相互补充。

至于短期,hook 刚刚被加入到 React。这可能是自 React 重写以来发生的最大变化,因为它们将带来更多可能性并增强更多功能组件(现在他们真的被大肆宣传)。

最后,正如我最近所做的那样,有React Native。对我来说,这是一项伟大的技术,在过去的几年中发生了很大的变化。 React Native正在重写它的核心,这应该以与 React 重写类似的方式完成(它全部是内部的,几乎没有任何东西应该为开发人员改变)。异步渲染成为本机和 JavaScript 之间更快更轻量级的桥梁。当然还有更多改变。

在 React 生态中有很多值得期待的东西,但 hook(以及React Native,如果有人喜欢手机应用的话)的更新可能将会是我们在2019年所能看到的最重要的变化。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容

  • 翻译:疯狂的技术宅原文:https://www.toptal.com/react/react-tutorial-p...
    疯狂的技术宅阅读 1,484评论 0 0
  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,428评论 1 33
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,050评论 2 35
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,813评论 1 18
  • 一整晚诡异的梦境终于将梦惊醒 黎明还未来 噩梦散落于空气之中 像机敏的失去母体的虫 潮水般涌过我的皮肤 散乱的情节...
    喃喃逸语阅读 197评论 0 2