简介

我们继续上一节的内容,开始分析 React 官网:https://reactjs.org/docs/accessibility.html 的 “高级指引” 部分,这一部分会涉及到性能优化、Portals、Render Props 等概念的分析,跟上节奏,我们一起出发吧!

知识点

  • 性能优化
  • Portals
  • Render Props
  • 类型检查

准备

我们直接用上一节中的 react-demo-day5 项目来作为我们的 Demo 项目,还没有创建的小伙伴可以直接执行以下命令 clone 一份代码:

git clone -b dev https://gitee.com/vv_bug/react-demo-day5.git

接着进入到项目根目录 react-demo-day5 ,并执行以下命令来安装依赖与启动项目:

npm install --registry https://registry.npm.taobao.org && npm start

等项目打包编译成功,浏览器会自动打开项目入口,看到上面截图的效果的时候,我们的准备工作就完成了。

性能优化

UI 更新需要昂贵的 DOM 操作,而 React 内部使用几种巧妙的技术以便最小化 DOM 操作次数。对于大部分应用而言,使用 React 时无需专门优化就已拥有高性能的用户界面。尽管如此,你仍然有办法来加速你的 React 应用。

shouldComponentUpdate 的作用

我们先贴一张官方的组件生命周期图:

可以看到,我们可以根据组件的 shouldComponentUpdate 方法去控制是否继续往下走 render 方法,以及后面的 节点 diff 对比,最后更新 DOM 节点等操作。

可以看到,我们在平时项目中,要尽量避免不必要的 render 操作,我们可以利用组件的 shouldComponentUpdate 方法去控制。

我们在 src/advanced-guides 目录下创建一个 optimizing 目录:

mkdir ./src/advanced-guides/optimizing

然后在 src/advanced-guides/optimizing 目录下创建一个 index.tsx 文件:

import React, {useState} from "react";
import HobbiesCom from "./hobbies.com";function Optimizing() {// 输入框 ref 引用let inputRef = React.createRef<HTMLInputElement>();// 额外的 state 数据let [count, setCount] = useState<number>(0);// hobbies 数据let [hobbies] = useState<Array<string>>(["打游戏", "做美食"]);/*** 添加 hobby*/function handleAddHobby() {if (inputRef.current) {setCount(++count);}}return (<div><input placeholder="请输入你的爱好" ref={ inputRef }/><button onClick={ handleAddHobby }>添加</button>{/* hobby 列表组件 */ }<HobbiesCom hobbies={ hobbies }/>{ count }</div>);
}export default Optimizing;

可以看到,我们简单创建了一个输入框,然后通过 “添加” 按钮修改了 count 的值,而 count 是我们额外定义的一个 State 数据。

接着我们在 src/advanced-guides/optimizing 目录下创建一个 hobbies.com.tsx 组件:

import React from "react";export type Prop = {hobbies: Array<string>,
};class HobbiesCom extends React.Component<Prop> {state = {choosed: -1}handleClick(index: number) {this.setState({choosed: index});}render() {return (<ul>{this.props.hobbies.map((hobby, index) => (<listyle={this.state.choosed === index ? {backgroundColor: "red"} : undefined}key={`hobby-${hobby}-index`}onClick={this.handleClick.bind(this, index)}>{hobby}</li>))}</ul>);}
}export default HobbiesCom;

可以看到,我们在 HobbiesCom 组件中渲染了 hobbies 的数据。

我们重新运行项目看结果:

npm start

可以看到,即使我们没有修改 HobbiesCom 组件中的 habbies 属性值,但是 HobbiesCom 组件仍然走了 render 函数,我们希望的是当 habbies 数组的数据没有变化的时候, HobbiesCom 组件不需要重新进行渲染,那我们该怎么做呢?

我们修改一下 src/advanced-guides/optimizing/hobbies.com.tsx 组件:

import React from "react";export type Prop = {hobbies: Array<string>,
};class HobbiesCom extends React.Component<Prop> {state = {choosed: -1}shouldComponentUpdate(nextProps: Prop, nextState: any) {// 浅对比新老属性的 hoobies 变量跟新老 state 的 choosed 属性return nextProps.hobbies !== this.props.hobbies || this.state.choosed !== nextState.choosed;}...
}export default HobbiesCom;

可以看到,只有当 shouldComponentUpdate 方法返回是 true 的时候,组件才会走 render 方法,我们对属性跟 State 做了一个浅对比,所以避免了不必要 render 方法的执行。

其实在 React 中,提供了一个 PureComponent 组件,它的作用就是在 shouldComponentUpdate 方法中对属性跟 State 进行了一个浅对比。

我们可以用 PureComponent 组件改造一下 src/advanced-guides/optimizing/hobbies.com.tsx 组件:

import React from "react";export type Prop = {hobbies: Array<string>,
};class HobbiesCom extends React.PureComponent<Prop> {state = {choosed: -1}handleClick(index: number) {this.setState({choosed: index});}render() {return (<ul>{ this.props.hobbies.map((hobby, index) => (<listyle={ this.state.choosed === index ? {backgroundColor: "red"} : undefined }key={ `hobby-${ hobby }-index` }onClick={ this.handleClick.bind(this, index) }>{ hobby }</li>)) }</ul>);}
}export default HobbiesCom;

可以看到,我们只需要把继承的 React.Component 改成 React.PureComponent 即可,效果跟上面的一样,我就不演示了哈,小伙伴自己跑跑。

React.memo

我们上面说的都是 “类组件” 中的性能优化,在 “类组件” 中我们可以通过自定义 shouldComponentUpdate 方法或者 React.PureComponent 来减少不必要的 render 操作,但是在 “函数式组件” 中我们该怎么做呢?

React17+ ,官方已经给给我们提供了一个高阶组件 React.memo

我们直接在 src/advanced-guides/optimizing 目录下创建一个 hobbies.func.tsx 函数式组件:

import React, {useState} from "react";export type Prop = {hobbies: Array<string>,
};function HobbiesFunc(props: Prop) {let [choosed, setChoosed] = useState(-1);function handleClick(index: number) {setChoosed(index);}return (<ul>{ props.hobbies.map((hobby, index) => (<listyle={ choosed === index ? {backgroundColor: "red"} : undefined }key={ `hobby-${ hobby }-index` }onClick={ () => {handleClick(index);} }>{ hobby }</li>)) }</ul>);
}function areEqual(prevProps: Prop, nextProps: Prop) {/*如果把 nextProps 传入 render 方法的返回结果与prevProps 传入 render 方法的返回结果一致则返回 true,否则返回 false*/return prevProps.hobbies === nextProps.hobbies;
}export default React.memo<Prop>(HobbiesFunc, areEqual);

可以看到,我们通过 React.memo 高阶组件中的第二个参数 areEqual 方法中做了判断,当新老 Props 中的 habbies 数据相等的时候,不执行组件的 render 操作。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现,所以我们 Demo 中其实不需要定义第二个参数,默认就可以达到我们想要的效果了。

效果就不演示啦,跟前面的类组件一致,小伙伴自己跑跑看效果哦。

我们分别通过 Demo 演示了 “类组件” 跟 “函数式组件” 中的性能优化,小伙伴一定要自己打断点跑跑看效果哦。

Portals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。

通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:

render() {// React 挂载了一个新的 div,并且把子元素渲染其中return (<div>      {this.props.children}</div>  );
}

然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:

render() {// React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。// `domNode` 是一个可以在任何位置的有效 DOM 节点。return ReactDOM.createPortal(this.props.children,domNode  );
}

一个 portal 的典型用例是当父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。

我们还是通过 Demo 来演示一下。

首先在 src/advanced-guides 目录下创建一个 portals 目录:

mkdir ./src/advanced-guides

接着我们在 src/advanced-guides/portals 目录下创建一个组件 index.tsx

import React, {useState} from "react";
import Model from "./model";function Portals() {let [isShowModel, setShowModel] = useState(false);function handleShow() {if (!isShowModel) {setTimeout(() => {setShowModel(false);}, 1000);}setShowModel((show) => {return !show;});}return (<React.Fragment>{isShowModel && (<Model><div className="model">hello model</div></Model>)}<div onClick={handleShow}>点我 show model</div></React.Fragment>);
}export default Portals;

可以看到,我们用了一个 isShowModel 状态去控制 Model 组件的展示。

然后我们在 src/advanced-guides/portals 目录下创建一个 model.tsx 组件:

import ReactDOM from "react-dom";function Model(props: any) {return ReactDOM.createPortal(props.children, document.body);
}export default Model;

很简单,我们直接用 ReactDOM.createPortal 定义并返回了一个 React 元素,并且把 Model 组件的子元素都绑定到了 document.body 节点上了。

接着我们去 src/main.scss 样式文件中添加一个 model 样式:

.root{font-size: 16px;
}
.model{position: fixed;top: 0;left: 0;right: 0;bottom: 0;width: 200px;height: 200px;margin: auto;line-height: 200px;text-align: center;background-color: rgba(0,0,0,0.7);
}

我们重新运行项目看结果:

可以看到,当我们点击 “show model” 按钮的时候,页面中显示了我们 Model 组件的子元素,并且这些子元素最后都被挂载到了 document.body 节点上了。

下面我们换一种实现方式,我们来实现一个 Toast 组件,让它能像 Model 组件一样,挂载到 body 节点上,然后指定时间后自动消失。

我们在 src/advanced-guides/portals 目录底下创建一个 toast.tsx 组件:

import ReactDom from "react-dom";
import React from "react";class Toast {static divEle: HTMLDivElement | null;static show(msg: string, timing: number = 1000) {Toast.hide();Toast.divEle = document.createElement("div");ReactDom.render((<div className="model">{msg}</div>),Toast.divEle);document.body.appendChild(Toast.divEle);setTimeout(() => {Toast.hide();}, timing);}static hide() {Toast?.divEle && document.body.removeChild(Toast.divEle);Toast.divEle = null;}
}export default Toast;

可以看到,我们用 ReactDom.render 创建了一个 React 根元素,把 Toast 的内容挂载到了一个 div 元素中,接着又把这个 div 元素挂在到了 document.body 节点上了。

我们修改一下 src/advanced-guides/portals/index.tsx 组件,用一下我们的 Toast 组件:

import React, {useState} from "react";
import Model from "./model";
import Toast from "./toast";function Portals() {let [isShowModel, setShowModel] = useState(false);function handleShow() {if (!isShowModel) {setTimeout(() => {setShowModel(false);}, 1000);}setShowModel((show) => {return !show;});}function apiShowModel() {Toast.show("你好呀", 2000);}return (<React.Fragment>{isShowModel && (<Model><div className="model">hello model</div></Model>)}<div onClick={handleShow}>点我 show model</div><div onClick={apiShowModel}>Api Show</div></React.Fragment>);
}export default Portals;

可以看到,我们在 apiShowModel 方法中调用了 Toast.show("你好呀", 2000) 方法创建了个一个 Toast

我们重新运行项目看结果:

可以看到,跟前面 Model 组件的效果一致。

下面我们用 ReactDom.createPortal 改造一下 Toast 组件:

import ReactDom from "react-dom";
import React from "react";class Toast {static divEle: React.RefObject<HTMLDivElement> | null;static show(msg: string, timing: number = 1000) {Toast.hide();Toast.divEle = React.createRef<HTMLDivElement>();ReactDom.render(ReactDom.createPortal((<div ref={Toast.divEle} className="model">{msg}</div>), document.body),document.createElement("div"));setTimeout(() => {Toast.hide();}, timing);}static hide() {Toast?.divEle?.current && document.body.removeChild(Toast.divEle.current);Toast.divEle = null;}
}export default Toast;

可以看到,我们把之前的 document.body.appendChild 改成了 ReactDom.createPortal,效果跟前面的一致,我就不演示啦,很明显了吧,小伙伴有没有弄懂 ReactDom.createPortal 的原理呢?

Render Props

术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,在 vueAngular 中叫 “插槽”。

具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。

说起来抽象,我们还是通过 Demo 来分析一下。

比如我们现在有一个组件支持 “头部”、“内容”、“尾部” 的展示,其中 “头部”、“尾部” 支持自定义方式。

我们首先在 src/advanced-guides 目录下创建一个 render-props 目录:

mkdir ./src/advanced-guides/render-props

然后我们在 src/advanced-guides/render-props 目录下创建一个 index.tsx 组件:

import PropTypes from "prop-types";type Prop = {renderHeader: () => React.ElementType,renderFooter: () => React.ElementType,
};function RenderProp(props: Prop | undefined) {return (<div style={{backgroundColor: "darkcyan"}}>{/*  渲染头部  */}{props?.renderHeader()}{/*  渲染内容  */}<div>我是 RenderProps 组件内容模块</div>{/*  渲染尾部  */}{props?.renderFooter()}</div>);
}RenderProp.propTypes = {renderHeader: PropTypes.func,renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {renderHeader: () => (<div>我是默认头部内容</div>),renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;

可以看到,我们创建了一个 RenderProp 组件,RenderProp 组件中渲染了三个部分 “头部”、“内容”、“尾部”,并且 “头部”、“尾部” 支持自定义。

我们修改一下 src/advanced-guides/index.tsx 组件,引入 RenderProp 组件:

/*** 核心概念列表*/
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";
function AdvancedGuides() {return (<ErrorBoundaries><div>{/* 代码分割 */ }<CodeSplit/>{/* Context */ }<Context/>{/* 报错的组件 */ }<ErrorCom/>{/* Refs 转发 */ }<ForwardRef/>{/* 性能优化 */ }<Optimizing/>{/* Portals */ }<Portals/>{/* Render Props */ }<RenderProps/></div></ErrorBoundaries>);
};
export default AdvancedGuides;

然后我们重新运行项目看结果:

可以看到,RenderProp 组件渲染了默认的内容。

接下来我们修改一下 src/advanced-guides/index.tsx 组件,自定义 RenderProps 的 “头部” 跟 “尾部” 内容:

/*** 核心概念列表*/
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";function AdvancedGuides() {return (<ErrorBoundaries><div>{/* 代码分割 */}<CodeSplit/>{/* Context */}<Context/>{/* 报错的组件 */}<ErrorCom/>{/* Refs 转发 */}<ForwardRef/>{/* 性能优化 */}<Optimizing/>{/* Portals */}<Portals/>{/* Render Props */}<RenderPropsrenderHeader={() => (<div>我是自定义头部内容</div>)}renderFooter={() => (<div>我是自定义尾部内容</div>)}/></div></ErrorBoundaries>);
};
export default AdvancedGuides;

然后重新运行项目看结果:

可以看到,我们成功的自定义了 RenderProp 组件的头部跟尾部内容。

有小伙伴要疑问了 “这样做的目的是什么呢?”我举个例子吧:

比如你的领导需要你开发一个 RenderProp 组件,告诉你需要支持 “头部”、“内容”、“尾部” 的渲染。ok,你能力很强,很快就完成了领导的需求,你开心的去休假去了,然后你领导把这个组件直接给到了另外一个开发手中,领导心血来潮了,说 “头部内容样式需要改改,赶紧把那个休假的人叫回来!!”,哈哈,这个时候你是不是就很无语了呢?那如果你的组件支持自定义功能,你就可以很牛逼的告诉领导:“ 不想用默认样式的话,支持自定义头部跟尾部的,爱怎么玩就怎么玩”。

使用 PropTypes 进行类型检查

随着你的应用程序不断增长,你可以通过类型检查捕获大量错误。对于某些应用程序来说,你可以使用 Flow 或 TypeScript 等 JavaScript 扩展来对整个应用程序做类型检查。但即使你不使用这些扩展,React 也内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes 属性,比如我们上面的 RenderProp 组件:

import PropTypes from "prop-types";type Prop = {renderHeader: () => React.ElementType,renderFooter: () => React.ElementType,
};function RenderProp(props: Prop | undefined) {return (...);
}RenderProp.propTypes = {renderHeader: PropTypes.func,renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {renderHeader: () => (<div>我是默认头部内容</div>),renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;

我们使用了 ts 静态类型校验跟 prop-types 的动态校验,如果在使用组件的时候不按规定传递属性类型的话,开发模式中直接就会报错了。

我们修改一下 src/advanced-guides/index.tsx 文件:

{/* Render Props */}<RenderPropsrenderHeader={1}renderFooter={() => (<div>我是自定义尾部内容</div>)}/>

可以看到,首先是 IDE 报错了,说我们需要的是 function 类型,但是你传递的是 number 类型,Webpack 编译也直接提示报错了。

接着是页面中的提示 :

PropTypes

以下提供了使用不同验证器的例子:

import PropTypes from 'prop-types';MyComponent.propTypes = {// 你可以将属性声明为 JS 原生类型,默认情况下// 这些属性都是可选的。optionalArray: PropTypes.array,optionalBool: PropTypes.bool,optionalFunc: PropTypes.func,optionalNumber: PropTypes.number,optionalObject: PropTypes.object,optionalString: PropTypes.string,optionalSymbol: PropTypes.symbol,// 任何可被渲染的元素(包括数字、字符串、元素或数组)// (或 Fragment) 也包含这些类型。optionalNode: PropTypes.node,// 一个 React 元素。optionalElement: PropTypes.element,// 一个 React 元素类型(即,MyComponent)。optionalElementType: PropTypes.elementType,// 你也可以声明 prop 为类的实例,这里使用// JS 的 instanceof 操作符。optionalMessage: PropTypes.instanceOf(Message),// 你可以让你的 prop 只能是特定的值,指定它为// 枚举类型。optionalEnum: PropTypes.oneOf(['News', 'Photos']),// 一个对象可以是几种类型中的任意一个类型optionalUnion: PropTypes.oneOfType([PropTypes.string,PropTypes.number,PropTypes.instanceOf(Message)]),// 可以指定一个数组由某一类型的元素组成optionalArrayOf: PropTypes.arrayOf(PropTypes.number),// 可以指定一个对象由某一类型的值组成optionalObjectOf: PropTypes.objectOf(PropTypes.number),// 可以指定一个对象由特定的类型值组成optionalObjectWithShape: PropTypes.shape({color: PropTypes.string,fontSize: PropTypes.number}),// An object with warnings on extra propertiesoptionalObjectWithStrictShape: PropTypes.exact({name: PropTypes.string,quantity: PropTypes.number}),// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保// 这个 prop 没有被提供时,会打印警告信息。requiredFunc: PropTypes.func.isRequired,// 任意类型的必需数据requiredAny: PropTypes.any.isRequired,// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。customProp: function(props, propName, componentName) {if (!/matchme/.test(props[propName])) {return new Error('Invalid prop `' + propName + '` supplied to' +' `' + componentName + '`. Validation failed.');}},// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。// 它应该在验证失败时返回一个 Error 对象。// 验证器将验证数组或对象中的每个值。验证器的前两个参数// 第一个是数组或对象本身// 第二个是他们当前的键。customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {if (!/matchme/.test(propValue[key])) {return new Error('Invalid prop `' + propFullName + '` supplied to' +' `' + componentName + '`. Validation failed.');}})
};

限制单个元素

你可以通过 PropTypes.element 来确保传递给组件的 children 中只包含一个元素。

import PropTypes from 'prop-types';class MyComponent extends React.Component {render() {// 这必须只有一个元素,否则控制台会打印警告。const children = this.props.children;return (<div>{children}</div>);}
}MyComponent.propTypes = {children: PropTypes.element.isRequired
};

默认 Prop 值

您可以通过配置特定的 defaultProps 属性来定义 props 的默认值:

class Greeting extends React.Component {render() {return (<h1>Hello, {this.props.name}</h1>);}
}// 指定 props 的默认值:
Greeting.defaultProps = {name: 'Stranger'
};// 渲染出 "Hello, Stranger":
ReactDOM.render(<Greeting />,document.getElementById('example')
);

如果你正在使用像 transform-class-properties 的 Babel 转换工具,你也可以在 React 组件类中声明 defaultProps 作为静态属性。此语法提案还没有最终确定,需要进行编译后才能在浏览器中运行。要了解更多信息,请查阅 class fields proposal。

class Greeting extends React.Component {static defaultProps = {name: 'stranger'}render() {return (<div>Hello, {this.props.name}</div>)}
}

defaultProps 用于确保 this.props.name 在父组件没有指定其值时,有一个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps

函数组件

如果你在常规开发中使用函数组件,那你可能需要做一些适当的改动,以保证 PropsTypes 应用正常。

假设你有如下组件:

export default function HelloWorldComponent({ name }) {return (<div>Hello, {name}</div>)
}

如果要添加 PropTypes,你可能需要在导出之前以单独声明的一个函数的形式,声明该组件,具体代码如下:

function HelloWorldComponent({ name }) {return (<div>Hello, {name}</div>)
}export default HelloWorldComponent

接着,可以直接在 HelloWorldComponent 上添加 PropTypes:

import PropTypes from 'prop-types'function HelloWorldComponent({ name }) {return (<div>Hello, {name}</div>)
}HelloWorldComponent.propTypes = {name: PropTypes.string
}export default HelloWorldComponent

上面的这些内容我们在前面的 Demo 中都有演示过,我们就不再演示了,小伙伴自己多敲敲哦!

总结

ok,React 的高级指引部分我们就算是分析完毕了,认认真真看到这里的小伙伴想必搞定面试跟简单的 React 项目应该是问题不大了,后面章节我们将会会介绍 React 中的 Hook、“全家桶”、“源码分析” 等等。

这节到这就结束啦,下节见~

欢迎志同道合的小伙伴一起交流,一起学习。

Demo 项目全部代码:https://gitee.com/vv_bug/react-demo-day5/tree/dev/

觉得写得不错的可以点点关注,帮忙转发跟点赞。

快来跟我一起学 React(Day8)相关推荐

  1. 快来跟我一起学 React(Day5)

    简介 上一节我们完成了从 0 开始搭建一个企业级 React 项目的全部内容,项目是有了,但是我们一直都没有近距离接触过 React,所以接下来我们就快速撸一遍 React 官方文档内容,弄清楚一些概 ...

  2. 有了vue为什么还学react?

    有了vue为什么还学react?因为react可弥补vue的不足.下面本篇文章就来给大家介绍一下.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. vue的优缺点 vue是Evan Y ...

  3. 手挽手带你学React:四档(上)一步一步学会react-redux (自己写个Redux)

    手挽手带你学React入门四档,用人话教你react-redux,理解redux架构,以及运用在react中.学完这一章,你就可以开始自己的react项目了. 之前在思否看到过某个大神的redux搭建 ...

  4. 从零学React Native之13 持久化存储

    数据持久化就是指应用程序将某些数据存储在手机存储空间中. 借助native存储 这种方式不言而喻,就是把内容传递给native层,通过原生API存储,详见从零学React Native之05混合开发 ...

  5. node都会 react_学react需要node吗

    学react需要node吗 学习react不需要安装node,react.js和node.js没有太大的关联性.完全可以独立的学习react.js. 但我们通常都会使用react提供的脚手架搭建项目结 ...

  6. 手挽手带你学React:三档 React-router4.x的使用

    手挽手带你学React入门三档,带你学会使用Reacr-router4.x,开始创建属于你的React项目 什么是React-router React Router 是一个基于 React 之上的强大 ...

  7. 小白学react之页面BaseLayout框架及微信的坑

    上一篇<小白学react之SASS实战>我们学习了如何通过运用sass来为我们的应用页面"上色",加入css的支持. 但是我们到现在为止,每个页面的标题还是系统默认的, ...

  8. 会Vue还有必要学React吗?

    这里说一下我的切身感受,为什么说学了Vue还要学React. Vue我有三年的开发经验,写过3个大型项目和其他的一些中小型项目,以前我一直觉得只要是能解决问题,用什么框架都一样,我把Vue学到极致就可 ...

  9. 跟我一起学 React + dva + Mockjs(9 个视频)

    跟我一起学 React + dva + Mockjs(9 个视频) 跟我一起学 React + dva + Mockjs #1 课程介绍「04:21」 跟我一起学 React + dva + Mock ...

最新文章

  1. python最新版本-Python 3.8 已发布 现在是切换至新版本的好时机吗?
  2. 滑动拼图验证码操作步骤:_拼图项目:延期的后果
  3. bs 程序用户个性化设置保存_想更改win10设置,这12种打开方法你不可不知,方便又快捷...
  4. Delphi Note
  5. LM算法+推导+C++代码实践
  6. 水利系统防雷,如何做好洪水预报警报系统的雷电防护
  7. 项目实训(十三)安装pun,pun的基础使用和概念
  8. 2022高教社杯 国赛数学建模 B题思路
  9. 零基础最详细html和css
  10. 【Python网络爬虫】基本原理
  11. 用 HI3559A / Hi3519A 接入 BT1120或BT656视频
  12. Golang 小技巧
  13. 深度解析推荐系统的算法原理
  14. 和讯网分行业和分省份社会责任指数(2010-2020年)
  15. 数据库开发面试问题汇总
  16. 喜大普奔——WIN10 虚拟机上可以跑NVIDIA CUDA,Hyper-v或是WSL?
  17. Batch Normalization究竟学到了数据的什么信息?
  18. 波奇宠物IPO的荣与忧:市场可期,但依赖性难解,还多次被罚
  19. 有哪些dns攻击类型呢?如何防护网站DNS不被恶意攻击?
  20. 关闭iTunes的强制自动文件备份 -(电脑空间不足,无法升级iPhone)

热门文章

  1. 目标检测: Camvid 语义标签转化为bbox标签
  2. InterSystems IRIS、IRIS for Health和HealthShare Health Connect的2021.1版本的预览版现在已经发布!...
  3. 浏览器的渲染机制-入门详细图解
  4. jdk源码写过注释后debug提示source code does not match the bytecode
  5. 启动报错this is very likely to create a memory leak
  6. ddr3ddr4 lpddr4速率_超极本的LPDDR3与笔记本的DDR4性能差多少
  7. B站《后浪》刷屏全网:谁不是一边实现梦想,一边和现实对抗?
  8. 网站快速通过ICP备案的方法!
  9. [附源码]计算机毕业设计springboot居家养老服务系统小程序
  10. 新农慕课python答案第零周答案_智慧树慕课答案农产品加工工艺学参考答案公众号...