avatar

目录
React

React核心的概念

虚拟DOM(Virtual Document Object Model)

  • DOM的本质是什么:就是用JS表示的UI元素
  • DOM和虚拟DOM的区别
    • DOM是由浏览器中的JS提供功能,所以我们只能人为的使用 浏览器提供的固定API来操作DOM对象
    • 虚拟DOM:并不是由浏览器提供的,而是我们程序员手动模拟实现的,类似于浏览器中的DOM,但是有着本质的区别

diff算法

脚手架(create-react-app)

一个React的脚手架,快速生成React的项目

React 的环境搭建,是比较繁琐的,有很多的依赖:reactreact-dombabelwebpack … 需要很多的前置知识,很容易让人从入门到放弃。

于是就诞生了 脚手架 这种东西,create-react-app 就是一个 React 的脚手架,用它可以很方便的就创建了整个 React 的环境搭建,它解决了所有的依赖问题。

目录结构

创建后,你的项目应如下所示:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
my-app/
README.md
node_modules/
package.json
public/
index.html
favicon.ico
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg

对于要构建的项目,这些文件必须以确切的文件名存在:

  • public/index.html 是页面模板;
  • src/index.js 是 JavaScript 入口点。

你可以删除或重命名其他文件。

你可以在 src 中创建子目录。 为了加快重新构建的速度,Webpack 只处理 src 中的文件。 你需要将任何 JS 和 CSS 文件放在 src,否则 Webpack 将发现不了它们。

添加TypeScript

TypeScript 是 JavaScript 的类型超集,可编译为纯 JavaScript 。

要使用 TypeScript 启动新的 Create React App 项目,你可以运行:

bash
1
2
3
$ npx create-react-app my-app --typescript
$ # 或者
$ yarn create react-app my-app --typescript

要将 TypeScript 添加到 Create React App 项目,请先安装它:

bash
1
2
3
$ npm install --save typescript @types/node @types/react @types/react-dom @types/jest
$ # 或者
$ yarn add typescript @types/node @types/react @types/react-dom @types/jest

接下来,将任何文件重命名为 TypeScript 文件(例如 src/index.js 重命名为 src/index.tsx )并 重新启动开发服务器

添加路由

要添加它,请运行:

sh
1
npm install --save react-router-dom

或者你可以使用 yarn:

sh
1
yarn add react-router-dom

要尝试它,删除 src/App.js 中的所有代码,并将其替换为其网站上的任何示例。 基本示例 是开始尝试的好地方。

可能会报错让安装npm install @types/react-router-dom

添加Bootstrap

从 npm 安装 reactstrap 和 Bootstrap 。 reactstrap 不包括 Bootstrap CSS 所以这也需要安装:

sh
1
npm install --save reactstrap bootstrap@4

或者你可以使用 yarn:

sh
1
yarn add bootstrap@4 reactstrap

src/index.js 文件的开头导入 Bootstrap CSS 和可选的 Bootstrap 主题 CSS :

Javascript
1
2
3
import 'bootstrap/dist/css/bootstrap.css';
// 在下面放置任何其他导入,
// 以便组件中的CSS优先于默认样式。

src/App.js 文件或自定义组件文件中导入所需的 reactstrap 组件:

Javascript
1
import { Button } from 'reactstrap';

JSX

实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

Javascript
1
2
3
4
5
6
7
8
9
10
11
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>

会编译为:

React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)

如果没有子节点,你还可以使用自闭合的标签形式

JSX 标签的第一部分指定了 React 元素的类型。

大写字母开头的 JSX 标签意味着它们是 React 组件。这些标签会被编译为对命名变量的直接引用,所以,当你使用 JSX 表达式时,Foo 必须包含在作用域内。

在 JSX 类型中使用点语法

在 JSX 中,你也可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,这会非常方便。例如,如果 MyComponents.DatePicker 是一个组件,你可以在 JSX 中直接使用

Javascript
1
2
3
4
5
6
7
8
9
10
import React from 'react';

const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}

function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;}

在运行时选择类型

我觉得应该没什么人这么用把

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
photo: PhotoStory,
video: VideoStory
};

function Story(props) {
// 正确!JSX 类型可以是大写字母开头的变量。
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}

props.children

包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件。有几种不同的方法来传递子元素:

React基础

开始

需要两个框架:

  • react
  • react-dom
  1. React.createElement(标签名,属性,内容)
  2. ReactDOM.render(创建的DOM,放到那个元素里)
    • warn:第二个参数是DOM元素

当页面中的元素过多的时候createElement的方式就会变得比较繁琐,
所以可以使用JSX语法,来帮助我们快速的生成标签,
但是JSX的本质还是把他们都转换为createElement的形式来创建标签,

使用React报错

  1. 使用JSX语法,需要用的插件是@babel/preset-react
  2. 然后在.babelrc中添加语法配置"presets": ["@babel/preset-react"]
  3. 还要配置相对应的loader
    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    test: /\.jsx?$/,
    exclude: /(node_modules|bower_components)/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: ['@babel/preset-env']
    }
    }
    },
  4. JSX的本质还是把他们都转换为createElement的形式来创建标签
  5. 如果要在JSX语法内部书写JS代码,那么所有的JS代码,必须写道{}里面去
    • 在编译的时候,如果遇到<>的时候,就把他当作HTML来解析,如果遇到{}就当作JS代码来解析
  6. 在JSX中,class属性,必须写成className,因为class在ES6中是一个关键字,label标签的for属性都与要替换成htmlFor
  7. 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹
  8. 如果写注释,也需要放到{}内部

React组件

注意:组件的名字必须使用大写,React 会将以小写字母开头的组件视为原生 DOM 标签

注意:函数组件会每次渲染都会渲染两次

在react创建组件的方式就是定义一个构造函数

  1. 创建组件的第一种方式
    • 定义一个构造函数
    • 这个函数中必须return标签或者null
  2. 用class类来继承React.Component创建组件
    • 在render函数中可以直接调用this.props来获取实参
    • 在consttructor中必须通过形参的方式来拿到实参,不能通过this.props来访问

用组件数组是使用forEach没效果

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 无状态组件(函数组件)渲染列表
export default function Hello(){
return <div>
{data.map((item,index)=>{
return <p key={index}>{item.id}----{item.name}</p>
})}
</div>
}


// 使用forEach不能遍历数组
export default function Hello(){
return <div>
{data.forEach((item,index)=>{
return <li>{item.id}-----{item.name}</li>
})}
</div>
}

区别:

  1. 在function定义的组件中,如果想要使用props必须先定义,否者无法直接使用
    但是,在class定义的组件中,可以直接使用this.props来直接访问,不需要预先接收

  2. 在class关键字创建的组件,内部除了由this.props这个只读属性之外,还有一个专门用于存放自己私有数据的this.state属性,这个state是可读可写的

  3. 使用function创建的组件叫做无状态组件也叫函数组件,使用class创建的组件叫做有状态组件

  4. class创建的组件,有自己的生命周期函数,function创建的组件,没有自己的生命周期函数,但是引入了Hook

应用场景:

  1. 如果一个组件需要存放自己的私有数据,或者需要组件在不同阶段执行不同的业务逻辑,此时非常适合用class船舰出来的有状态组件

  2. 如果一个组件,只需要根据外界传递过来的props渲染固定的页面结构,此时,非常适合使用function创建出来的无状态组件;(使用无状态组件的好处:由于没有了组件的生命周期函数,所以运行速度会相对块一点点)

组件中写样式

  1. 通过import进来一个CSS样式表
  2. 如果要写内联样式表,则需要用{}包裹,第一层{}是表示写JS代码,第二层表示一个对象
  3. 如果内联样式表中的单位是px则可以不用写
  4. CSS的模块化

管理组件

当组件多的时候,不可能都放在一个JS当中,都是要抽离出去来管理
这时抽离出去的文件也是JS,但是JS语法中写HTML是没有智能提示,这时可以把文件的后缀名改成JSX,
但是webpack打包不识别,

组件传值

  1. 父组件向子组件传值,不管是属性还是方法,都可以动过 this.props来直接调用,
    而Vue中属性需要通过 props 来接收,方法需要用 this.$emit() 来调用
  2. 父组件向子组件用ref绑定一个字符串后,父组件可以通过this.refs.字符串来拿到子组件的所有参数
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children} </div>
);
}

function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}

JSX标签中的所有内容都会作为一个 children prop 传递给 FancyBorder 组件。因为 FancyBorder{props.children} 渲染在一个 <div> 中,被传递的这些子组件最终都会出现在输出结果中。

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left} </div>
<div className="SplitPane-right">
{props.right} </div>
</div>
);
}

function App() {
return (
<SplitPane
left={
<Contacts /> }
right={
<Chat /> } />
);
}

<Contacts /><Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。

组件的默认值

Javascript
1
2
3
4
5
6
7
class Person extends React.Component {
static defaultProps = {
init: 11
}
}

// 固定写法
  • 注意:
    • 如果父组件传值则会默认替换掉,无需手动替换
    • 如果默认值有的属性,传值却没有的属性将会清空

案例1: 使用.map函数来渲染数组
案例2: 把案例1的渲染的数组抽取两个组件,有状态组件和无状态组件
案例3: 样式优化
案例4: 把样式抽离到单独的JSX文件里去
案例5: 使用本地存储,配合生命周期函数,写出评论列表案例,

事件

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串c。

在阻止默认行为的时候需要在事件处理函数中调用e.preventDefault()

如果你想异步访问事件属性,你需在事件上调用 event.persist(),此方法会从池中移除合成事件,允许用户代码保留对事件的引用。

this指向问题

class中的方法不会默认绑定this

class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this

三种解决方法:

  • 在使用函数的时候用bind方法绑定
  • 创建方法的时候使用箭头函数
  • 在onclick中使用onClick={()=> this.fun()}
    • 但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。

传递参数

Javascript
1
2
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过箭头函数Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

React组件的生命周期

我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:这些方法叫做“生命周期方法”。

React16.4的最新生命周期:

挂载:

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  1. constructor()
  2. static getDerivedStateFromProps()
  3. render():渲染DOM,render执行完,内存中就有了完整的虚拟DOM了
    • 等于Vue中的beforeMount
  4. componentDidMount: 组件完成了挂载,组件已经显示到了页面上,当这个方法执行完了,组件就进入运行阶段了
    • 等于Vue中的mounted

更新:

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序:

  1. static getDerivedStateFromProps()
  2. shouldComponentUpdate(nextProps,nextState):state发生改变时调用
    • 组件将要被更新,此时,组件尚未开始更新,但是,形参中的stateprops肯定是最新的
    • shouldComponentUpdate中要求必须返回一个布尔值。在shouldComponentUpdate中,如果返回值是false,则不会继续执行后续的生命周期函数,而是直接退回到了运行中的状态,此时有后续的render函数并没有被调用,因此,页面不会更新,但是,组件中的state状态,却被修改了
  3. render
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate(prevProps,prevState)

卸载:

  • componentWillUnmount:组件将要被卸载,此时组件还可以正常使用

不推荐使用已经:

  1. UNSAFE_componentWillReceiveProps:props发生改变时调用

    • 组件将要接收新属性,此时,只要这个方法被出发,就证明父组件为当前子组件传递新的属性值。此函数里有一个参数nextProps
    • 注意:在props没发生改变的时候也会触发,需自行去判断this.props和nextProps的值做对比
  2. UNSAFE_componentWillUpdate(nextProps,nextState)

  3. UNSAFE_componentWillMount: 组件将要被挂在,此时还没有开始渲染虚拟DOM

    • 等于Vue中的created

生命周期注意点

constructor
  • 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

  • 避免将 props 的值复制给 state!这是一个常见的错误

注意:在React16.9中对一些生命周期函数进行了重命名,如下

  • componentWillMount → UNSAFE_componentWillMount
  • componentWillReceiveProps → UNSAFE_componentWillReceiveProps
  • componentWillUpdate → UNSAFE_componentWillUpdate

旧的生命周期函数还是可以使用的,只是使用的时候会报警告,在React17中会把这些旧生命周期进行移除。

当然更新了这些生命周期函数的前缀也已经告你你了是不安全的UNSAFE === 不安全的。所以即使改了新名字,也是不推荐使用的。为此官方出现了一些新的生命周期函数。但是又不推荐使用,官方自己也贴上了不常用使用的标签

  • static getDerivedStateFromProps(props, state)

  • getSnapshotBeforeUpdate(prevProps, prevState)

实现双向数据绑定

在 React 中没有向Vue中有个 v-modeul 属性可以直接事项双向数据绑定
在 React 中没有提供这个指令或者 API
所以要想实现双向数据绑定,要靠 onChange 事件

  1. 在 Vue 中,有 v-model 指令来实现双向数据绑定,但是,在 React 中,根本没有指令的概念,因此 React 默认也不支持双向数据绑定

  2. React只支持,把数据传输到页面,但是,无法自动实现数据从页面传输到state中进行保存,也就是React不支持数据的自动逆向传输,只是实现了数据的单项绑定

  3. 在自己实现双向数据绑定时,一定会用到input,如果input的value值绑定了state的值,那么,必须
    同时为表单元素绑定 readOnly ,或者提供 onChange 事件

    1. 如果提供了readOnly 表示这个元素只读不能被修改
    2. 如果提供了onChange 表示这个元素的值可以被修改,但是要自己定义修改的逻辑
  4. 实现

    • 获取元素
      1. 使用 document.getElementById的方式来获取DOM 元素
      2. 使用 ref 来获取元素
      3. 使用事件机制的 event 对象来获取元素
        • 如果你想异步访问事件属性,你需在事件上调用 event.persist(),此方法会从池中移除合成事件,允许用户代码保留对事件的引用。
    • 数据回邦
      1. 通过 this.setState 给他绑定回去,即可实现双向数据绑定

key

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key。

如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。

受控组件

在React中表单元素通常都会提交

而Ract不喜欢直接使用form中action方式去提交数据,而是在提交按钮绑定方法去提交,这样可以绑定state可以更好的拿到数据

在使用select组件的时候,给需要默认显示的项添加selected是无效的,需要在select标签中用value来指定默认展示项

Javascript
1
2
3
4
5
6
 <select value="coconut" onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>

受控输入空值

受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefinednull

下面的代码演示了这一点。(输入最初被锁定,但在短时间延迟后变为可编辑。)

Javascript
1
2
3
4
5
ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

State & 数据校验

不要直接修改state

不要直接this.state.comment = "hello"
而是 this.setState({comment: "hello"})

State 的更新可能是异步的

setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。

出于性能考虑,React可能会把多个setState()调用合并成一个调用

因为this.propsthis.state可能会异步更新,所以你不要依赖他们的值来更新下一个状态

因此可能没及时的拿到数据

要解决上面这个问题,可以让setState()接收一个函数而不是一个对象。这个函数用上了一个state作为日第一个参数,将此次更新被应用时的props作为第二个参数:

Javascript
1
2
3
this.setState((state, props) => ({
counter: state.counter + props.increment
}));

或者通过return一个对象

Javascript
1
2
3
4
5
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});

State的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

Javascript
1
2
3
4
5
6
7
8
9
10
11
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts });
});

fetchComments().then(response => {
this.setState({
comments: response.comments });
});
}

这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments

在传值的时候,不知道该传什么类型的值时,我们可以在代码中规定好,如果不符合对应的数据类型就会报错
需要React提供的第三方包来实现prop-types
引入的时候必须大写

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
import PropTypes from "prop-types"

class Person extends React.Component {

}

Person.propTypes = {
name: PropTypes.string;
}


// 固定写法
// 从version 15.5抽离成单独的包
  • 修改状态里的值
    跟vuex的理念一样,不推荐直接操作,需要有个函数包装一下
  • ref的用法
    React中的ref用法跟Vue中差不多
    Code
    1
    2
    3
    4
    5
    6

    <div ref="div1"></div>


    console.log(this.refs.div1); // React中的用法
    console.log(this.$refs.div1); // Vue中的用法

React进阶

代码分割

在你的应用中引入代码分割的最佳方式是通过动态 import() 语法。

使用之前:

Javascript
1
2
3
import { add } from './math';

console.log(add(16, 26));

使用之后:

Javascript
1
2
3
import("./math").then(math => {
console.log(math.add(16, 26));
});

注意三点:

  • 当 Webpack 解析到该语法时,会自动进行代码分割。如果你使用 Create React App,该功能已开箱即用,你可以立刻使用该特性。Next.js 也已支持该特性而无需进行配置。

  • 如果你自己配置 Webpack,你可能要阅读下 Webpack 关于代码分割的指南。你的 Webpack 配置应该类似于此

  • 当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 babel-plugin-syntax-dynamic-import 插件。

懒加载

懒加载其实就是延时加载,即当对象需要用到的时候再去加载。提高性能

使用之前:

Code
1
import OtherComponent from './OtherComponent';

使用之后:

Code
1
const OtherComponent = React.lazy(() => import('./OtherComponent'));

此代码将会在组件首次渲染时,自动导入包含 OtherComponent 组件的包。

React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

然后应在 Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。

Ref

下面的例子已经更新为使用在 React 16.3 版本引入的 React.createRef() API。如果你正在使用一个较早版本的 React,我们推荐你使用回调形式的 refs

创建Ref

  • this.myRef = React.createRef()

    Javascript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyComponent extends React.Component {
    constructor(props) {
    super(props);
    this.myRef = React.createRef();
    }
    render() {
    return <div ref={this.myRef} />;
    }
    }

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。

为class组件添加Ref

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef(); }

componentDidMount() {
this.textInput.current.focusTextInput(); }

render() {
return (
<CustomTextInput ref={this.textInput} /> );
}
}

请注意,这仅在 CustomTextInput 声明为 class 时才有效:

Refs 与函数组件

默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例

不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它 const textInput = useRef(null);
function handleClick() {
textInput.current.focus(); }

return (
<div>
<input
type="text"
ref={textInput} /> <input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}

回调Ref

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.setTextInputRef = "";
}

render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={element => this.setTextInputRef = element} />
</div>
);
}
}

Ref 转发

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}

const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

这样,使用 FancyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。

以下是对上述示例发生情况的逐步解释:

  1. 我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
  2. 我们通过指定 ref 为 JSX 属性,将其向下传递给 ``。
  3. React 传递 refforwardRef 内函数 (props, ref) => ...,作为其第二个参数。
  4. 我们向下转发该 ref 参数到 ``,将其指定为 JSX 属性。
  5. 当 ref 挂载完成,ref.current 将指向 `` DOM 节点。

注意

第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref

Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。

Fragments

通常我们都会有一个需求

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
return (
<div>
<ul>
<List data={data}/>
</ul>
</div>
)


function List(props){
return (
<div>
{props.data.map(item => {
return <li>{item}</li>
})}
</div>
)
}

由于List组件需要一个根元素,所以使用了div作为的根元素,但是再ul列表里放置div明显不符合语义

使用Fragments解决问题

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function List(props){
return (
<React.Fragment>
{props.data.map(item => {
return <li>{item}</li>
})}
</React.Fragment>
)
}

或者使用空标签

function List(props){
return (
<>
{props.data.map(item => {
return <li>{item}</li>
})}
</>
)
}

当然Fragments也可以带key属性

Context

  1. 当组件的嵌套光系开始变得复杂时,传递数据将会变得很麻烦
  2. 解决方法:用 context 去解决
  3. context 不同于 Vuex , context运用其来比较麻烦
  4. 应用场景
    1. 当许多组件需要运用到同一个数据时
  5. 用法:
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
第一种用法,老用法(不推荐使用)

class father extends React.Component {

constructor(props){
super(props);

this.state = {
color: "red";
}
}

1. 在父组件中,定义一个 function ,这个 function 有固定名称,叫做 getChildContext ,内部,必须返回一个对象,这个对象,就是要共享给所有子组件的数据

getChildContext(){
return {
color: this.state.color
}
}

2. 使用属性校验,规定一下传递给 子组件的数据类型,需要定义一个静态属性 childContextTypes ,固定名称

static childContextTypes = {
color: PropTypes.string
}

}



class son extentds React.Component {

3. 在子组件,先进行属性校验,去校验父组件床底过来的参数类型
static contextTypes = {
color: PropTypes.string
}


render(){
return <div>
<p style={{color:this.context.color}}>hello</p>
</div>
}
}
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
第二种用法,新用法

import React,{Component,createContext} from "react";

const BatteryContext = createContext();

//声明一个孙组件
class Leaf extends Component {
constructor(props){
super(props);
}
render() {
return (
<BatteryContext.Consumer>
{
value => <h1>Battery : {value}</h1>
}
</BatteryContext.Consumer>
)
}
}

//声明一个子组件
class Middle extends Component {
render() {
return <Leaf />
}
}

class App extends Component {
render(){
return (
<BatteryContext.Provider value={60}>
<Middle />
</BatteryContext.Provider>
);
}
}

export default App;

高阶组件(未理解)

如果一个函数 接受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件。

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';

const Button = (props) => <button>{props.children}</button> //无状态组件

class Label extends React.Component{//传统组件

render(){
return(
<label>{this.props.children}</label>
)
}
}

class App extends React.Component{//根组件
render(){
return(
<div>
<Button>button</Button>
<br/>
<Label>label</Label>
</div>
)
}
}
module.exports = App

性能优化(还没看)

Profiler

Portals(没理解)

插槽能将子节点渲染到父组件的DOM层次之外

用法:ReactDOM.createPortal(child, container)

不使用ES6和JSX(了解即可)

ES6的有状态组件可以使用create-react-class模块

render props

是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

最直观给出两段代码,就能看出区别:

之前:

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// App父组件
function App (){
return (
<div>
<Mouse />}
</div>
)
}

// Mouse子组件
class Mouse extends React.Component {
handleMouseMove(e){
const {clientX,clientY} = e;
this.setState({
x:clientX,
y:clientY
})
}

render(){
return (
<div className="Mouse" onMouseMove={this.handleMouseMove}>
<p>position is ({x},{y})</p>
<Cat {...this.state}/>
</div>
)
}
}

// Cat孙子组件
function Cat(props){
return (
<p>position is {props.x},{props.y}</p>
)
}

render props:

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// App父组件
function App (){
return (
<div>
<Mouse render={props => <Cat {...props} />}
</div>
)
}

// Mouse子组件
class Mouse extends React.Component {
handleMouseMove(e){
const {clientX,clientY} = e;
this.setState({
x:clientX,
y:clientY
})
}

render(){
return (
<div className="Mouse" onMouseMove={this.handleMouseMove}>
<p>position is ({x},{y})</p>
{this.props.render({x,y})}
</div>
)
}
}

// Cat孙子组件
function Cat(props){
return (
<p>position is {props.x},{props.y}</p>
)
}

这里同样都是在使用Mouse组件的坐标计算,而在之前我们是在Mosue组件中已经写死了导致与复用性基本为0.

而在用了render props之后我们只需要在使用Mouse组件的时候指定render属性指定子组件即可,这样如果两个组件都想用Mouse组件的坐标就可以很完美的解决问题

使用Props而非render

重要的是要记住,render prop 是因为模式才被称为 render prop ,你不一定要用名为 render 的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”.

尽管之前的例子使用了 render,我们也可以简单地使用 children prop!

Javascript
1
2
3
<Mouse children={mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}/>

记住,children prop 并不真正需要添加到 JSX 元素的 “attributes” 列表中。相反,你可以直接放置到元素的内部

Javascript
1
2
3
4
5
<Mouse>
{mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}
</Mouse>

将 Render Props 与 React.PureComponent 一起使用时要小心

静态类型检测

FlowTypeScript 等这些静态类型检查器,可以在运行前识别某些类型的问题。他们还可以通过增加自动补全等功能来改善开发者的工作流程。出于这个原因,我们建议在大型代码库中使用 Flow 或 TypeScript 来代替 PropTypes

Flow

Flow 是一个针对 JavaScript 代码的静态类型检测器。Flow 由 Facebook 开发,经常与 React 一起使用。Flow 通过特殊的类型语法为变量,函数,以及 React 组件提供注解,帮助你尽早地发现错误。你可以阅读 introduction to Flow 来了解它的基础知识

TypeScript

TypeScript 是一种由微软开发的编程语言。它是 JavaScript 的一个类型超集,包含独立的编译器。作为一种类型语言,TypeScript 可以在构建时发现 bug 和错误,这样程序运行时就可以避免此类错误。您可以通过此文档 了解更多有关在 React 中使用 TypeScript 的知识。

严格模式

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';

function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}

StrictMode 目前有助于:

路由

react-router:是基本的router包,里边函的内容较多,但是在网页开发中有很多用不到,现在的市面上的课程讲的基本都是这个包的教程。

react-router-dom:随着react生态环境的壮大,后出现的包,这个包比react-router包轻巧了很多。

引入包之后要按需导出三个属性

import {BrowserRouter as Router , Route , Link } from 'react-router-dom';
import {HashRouter , Route , NavLink , Redirect } from 'react-router-dom';

  1. Rouer表示一个路由的根容器,所有跟路由相关的东西,都要被包裹在 Router 里面

    • 一个网站在,只需要使用一次 Router 就好了
    • 在一个Router中,只能有唯一的一个根元素
  2. HashRouter的用法是跟Router一样的,HashRouter会有/#

  3. Route 表示一个路由规则,在Route上,有两个比较重要的属性,path 和 component

    • Route 创建的标签,就是路由规则,path 和 component 的用法跟 Vue 一样
    • 这个标签既是路由匹配规则,也是一个占位符
  4. Link 表示一个路由的链接,就好比 Vue中的

  5. NavLink

用法

Code
1
2
3
4
5
6
7
8
9
10
11
12
ReactDOM.render(<Router>
<div>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
</div>


<Redirect path="/" to="/home" />
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>

</Router>, document.querySelector("#app"));
  • 路由的默认匹配是模糊匹配
    • 也就是说如果规则是 /home,路由是/home/12也能匹配的上
    • 解决方法: 在Router中添加 exact 的属性变成精准匹配
  • 当俩个路由很相像的时候精准匹配也会匹配上两个路由
    • 也就是说/movie/detail/2222路由时
    • 规则是:movie/detail/:id/movie/:type/:page,就算是精准匹配两个路由也都是会匹配到
    • 解决方法:在Router包里导入switch组件,如果匹配到了第一个则不会继续匹配

路由的参数

如果想要从路由规则中,提取匹配到的参数,进行使用,可以使用 this.props.match.params 来访问

路由的传参

  • 第一种:通过url传参

    • <Link to="/home?username=sz">Home</Link>通过this.props可以查看到具体的参数
  • 第二种:隐式路由传参

    • 推荐使用,比较安全,获取传递参数都比较方便
    Code
    1
    2
    3
    4
    5
    6
    <Link to={{
    pathname: 'about',
    state: {
    name: 'dx'
    }
    }}>关于</Link>
  • 第三种:组件之间的传值

    • 在正常情况下我们的路由都是这么写的<Route path='/test' component={Test} />这样当要传递一些方法的时候就不行了
    • 改造使用render
    Code
    1
    2
    3
    4
    5
    6
    7
    8
    <Link to='/test'>测试</Link>
    <Route path='/test' render={(routeProps) => {
    //routeProps就是路由组件传递的参数
    return (
    //在原先路由组件参数的情况,扩展绑定父组件对子组件传递的参数
    <Test {...routeProps} name='dx' age={18} />
    )
    }}></Route>
  • 第四种:withRouter

    • 想要在某个子组件中获取路由的参数,必须得使用路由中的route标签的子组件才能被绑定上路由的参数。为了解决不通过route标签绑定的子组件获取路由参数的问题,需要使用withRouter,一般用在返回首页,返回上一级等按钮上

CSS解决方案

在用webpack打包只能用引入scss和less或者css来写样式,又不像vue中一个组件有专门写样式的地方

请求数据

  • 用ES6的Fetch
    • 但是Fetch不能跨域,所以要使用第三方包 fetch-jsonp

API

组件

使用React组件可以将UI拆分为独立且复用的代码片段

可通过以下两种方式来定义组件

  • React.Component
  • React.PureComponent

React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数

React.component

React.Component 的子类中有个必须定义的 render() 函数。本章节介绍其他方法均为可选。

创建React元素

建议使用JSX来编写。每个JSX元素都是调用React.createElement的语法糖

如果使用JSX,就不在需要调用以下两个方法

  • createElement
  • createFactory

createElement

Javascript
1
React.createElement( type, [props], [...children])

使用 JSX 编写的代码将会被转换成使用 React.createElement() 的形式。如果使用了 JSX 方式,那么一般来说就不需要直接调用 React.createElement()

cloneElement

Javascript
1
React.cloneElement(element, [props], [...children])

element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 keyref 将被保留。

createFactory

此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。

Refs

React.createRef

React.createRef 创建一个能够通过 ref 属性附加到 React 元素的 ref

React.forwardRef

React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

React.forwardRef 接受渲染函数作为参数。React 将使用 propsref 作为参数来调用此函数。此函数应返回 React 节点。

Javascript
1
2
3
4
5
6
7
const FancyButton = React.forwardRef((props, ref) => (  <button ref={ref} className="FancyButton">    {props.children}
</button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

在上述的示例中,React 会将 元素的 `ref` 作为第二个参数传递给 `React.forwardRef` 函数中的渲染函数。该渲染函数会将 `ref` 传递给 元素。

因此,当 React 附加了 ref 属性之后,ref.current 将直接指向 `` DOM 元素实例。

Suspense

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy 动态加载组件

React.lazy

React.lazy() 允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。

注意

使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。

React.Suspense

React.Suspense 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。目前,懒加载组件是 `` 支持的唯一用例:

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}

Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 拥有专属文档章节和单独的 API 参考文档:

什么是Hooks

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook和自定Hook。不要在其他 JavaScript 函数中调用。

什么是Hooks,先看一个简单的例子

这是个点击按钮让count+1的例子

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

用Hooks实现相同功能

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState } from 'react';

function Example() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

React为什么要搞一个Hooks

为什么要搞一个Hooks,既然想用状态可以用class创建的组件?

上面的问题确实说的也没错,但是组件之所以创建不就是能够复用吗,当每一个组件都拥有了自己的状态,复用基本不太可能了。在Hooks诞生前,官方推荐的两种方式 渲染属性(Render Props) 和 高阶组件(Higher-Order Component)

这两种方式看起来都挺好的,但是嵌套关系会进一步扩大

渲染属性

渲染属性值得是使用一个值为函数的prop来传递需要动态渲染的nodes或组件

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";

export default class RenderComponent extends React.Component {
constructor(props) {
super(props);
this.state = { target: "zrb" }
}

render() {
return (
<div>{this.props.render(this.state)}</div>
)
}
}

<RenderComponnet render={ (data)=>{
<component getData={data} />
} }/>
高阶组件

一个函数接受一个组件作为参数,经过一系列的加工后,最后返回一个新的组件

Code
1
2
3
4
5
6
7
8
9
10
11
const withUser = WrappedComponent => {
const user = sessionStorage.getItem("user");
return props => <WrappedComponent user={user} {...props} />;
};

const UserPage = props => (
<div class="user-container">
<p>My name is {props.user}!</p>
</div>
);
export default withUser(UserPage);

State Hooks

useState是react自带的一个hook函数,他的作用接收来声明状态变量。useState这个函数接收的参数是状态的初始值,他返回一个数组,这个数组的第0个是当前的状态值,第一个值是可以改变状态值的方法函数

Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作

你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);

// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => { // 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

可以通过return取消订阅effect

effect的第二个参数:

  • 如果什么都不传,那么每次渲染都会执行
  • 人如果传一个空数组,那么就只会运行一次effect,并且efeect内部的props和state会一直拥有初始值
  • 如果传的是一个包含属性值的数组,那么只有当数组里的值发生变化才会触发回调

useRef

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const refContainer = useRef(initialValue);
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

React+TS实战

报错一:

构造函数里的参数报错

解决方法:

把构造函数里的参数定义成any类型

报错二:

使用this.state地下的值时,报错说什么只读啥的

解决方案:

在class声明的后面加上类型any,例如

export default class slidingBlock extends React.Component<any, any>

报错三:

Type ‘void’ is not assignable to type ‘((event: MouseEvent<HTMLLIElement, MouseEvent>) => void) | undefined’. TS2322

解决方案:

Javascript
1
<li key={item.name} onClick={clickActive(item.select)}><a href="#/">{item.name}</a></li>

改成:

Javascript
1
<li key={item.name} onClick={()=>clickActive(item.select)}><a href="#/">{item.name}</a></li>

报错四:

脚手架:The href attribute is required for an anchor to be keyboard accessible.
Provide a valid, navigable address as the href value.
If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles.
Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md jsx-a11y/anchor-is-valid

解决方案;

  • 先把代码提交了

  • 再运行npm run eject

  • package.json文件中添加如下代码

1
2
3
4
5
6
"eslintConfig": {
"extends": "react-app",
"rules":{
"jsx-a11y/anchor-is-valid":"off"
}
}
  • 可能会出现node-sass包的问题,重新安装即可npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

报错五:

Useless constructor @typescript-eslint/no-useless-constructor

解决方法:

解决方法只要在constructor里面加上this.state={ },加上state对象就可以

文章作者: 青空
文章链接: https://gitee.com/AIR-ZRB/blog/2020/06/27/React-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 青空
打赏
  • 微信
    微信
  • 支付寶
    支付寶