作者: 二传二改请声明
全局安装create-react-app 脚手架
$ npm install -g create-react-app
创建一个项目
$ create-react-app your-app 注意命名方式
Creating a new React app in /dir/your-app.
Installing packages. This might take a couple of minutes. 安装过程较慢,
Installing react, react-dom, and react-scripts...
这需要等待一段时间,这个过程实际上会安装三个东西
出现下面的界面,表示创建项目成功:
Success! Created your-app at /dir/your-app
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd your-app
npm start
Happy hacking!
根据上面的提示,通过cd your-app
命令进入目录并运行npm start
即可运行项目。
生成项目的目录结构如下:
├── README.md 使用方法的文档
├── node_modules 所有的依赖安装的目录
├── package-lock.json 锁定安装时的包的版本号,保证团队的依赖能保证一致。
├── package.json
├── public 静态公共目录
└── src 开发用的源代码目录
常见问题:
npm cache clean --force
之后再执行yarn命令React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
轻量级的视图层库!A JavaScript library for building user interfaces
React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
React高性能的原理:
在Web开发中我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。
React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A-B, B-A,React会认为A变成B,然后又从B变成A UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。
尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
React Fiber:
在react 16之后发布的一种react 核心算法,React Fiber是对核心算法的一次重新实现(官网说法)。之前用的是diff算法。
在之前React中,更新过程是同步的,这可能会导致性能问题。
当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树,这整个过程是同步进行的,也就是说只要一个加载或者更新过程开始,中途不会中断。因为JavaScript单线程的特点,如果组件树很大的时候,每个同步任务耗时太长,就会出现卡顿。
React Fiber的方法其实很简单——分片。把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
(1) 虚拟DOM
我们以前操作dom的方式是通过document.getElementById()的方式,这样的过程实际上是先去读取html的dom结构,将结构转换成变量,再进行操作。而reactjs定义了一套变量形式的dom模型,一切操作和换算直接在变量中,这样减少了操作真实dom,性能真实相当的高,和主流MVC框架有本质的区别,并不和dom打交道
(2) 组件系统
ui组件、容器组件、受控组件、非受控组件、类组件、函数式组件、高阶组件
react最核心的思想是将页面中任何一个区域或者元素都可以看做一个组件 component
那么什么是组件呢?
组件指的就是同时包含了html、css、js、image元素的聚合体
使用react开发的核心就是将页面拆分成若干个组件,并且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式
(3) 单向数据流
其实reactjs的核心内容就是数据绑定,所谓数据绑定指的是只要将一些服务端的数据和前端页面绑定好,开发者只关注实现业务就行了
(4) JSX 语法
在vue中,我们使用render函数来构建组件的dom结构性能较高,因为省去了查找和编译模板的过程,但是在render中利用createElement创建结构的时候代码可读性较低,较为复杂,此时可以利用jsx语法来在render中创建dom,解决这个问题,但是前提是需要使用工具来编译jsx
react开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在里面重新创建一个index.js. 写入以下代码:
// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'
// ReactDOM里有一个render方法,功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的元素上
ReactDOM.render(
// 这里就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。语法错误吗?这并不是合法的 JavaScript 代码, “在 JavaScript 写的标签的”语法叫 JSX- JavaScript XML。
欢迎进入React的世界
,
// 渲染到哪里
document.getElementById('root')
)
如果代码多了之后,不可能一直在render方法里写,所以就需要把里面的代码提出来,定义一个变量,像这样:
import React from 'react'
import ReactDOM from 'react-dom'
// 这里感觉又不习惯了?这是在用JSX定义一下react元素
const app = 欢迎进入React的世界
ReactDOM.render(
app,
document.getElementById('root')
)
由于元素没有办法传递参数,所以我们就需要把之前定义的变量改为一个方法,让这个方法去return一个元素:
import React from 'react'
import ReactDOM from 'react-dom'
// 特别注意这里的写法,如果要在JSX里写js表达式(只能是表达式,不能流程控制),就需要加 {},包括注释也是一样,并且可以多层嵌套
const app = (props) => 欢迎进入{props.name}的世界
ReactDOM.render(
app({
name: 'react'
}),
document.getElementById('root')
)
这里我们定义的方法实际上也是react定义组件的第一种方式-定义函数式组件,这也是无状态组件。但是这种写法不符合react的jsx的风格,更好的方式是使用以下方式进行改造
import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => 欢迎进入{props.name}的世界
ReactDOM.render(
// React组件的调用方式
,
document.getElementById('root')
)
这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。
ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class
是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
// 注意这里得用this.props.name, 必须用this.props
欢迎进入{this.props.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
运行结果和之前完全一样,因为JS里没有真正的class,这个class只是一个语法糖, 但二者的运行机制底层运行机制不一样。
函数式组件是直接调用, 在前面的代码里已经有看到
es6 class
组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
欢迎进入{this.props.name}的世界
)
}
}
const app = new App({
name: 'react'
}).render()
ReactDOM.render(
app,
document.getElementById('root')
)
在16以前的版本还支持这样创建组件, 但现在的项目基本上不用
React.createClass({
render () {
return (
{this.props.xxx}
)
}
})
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖
组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系
// 从 react 的包当中引入了 React 和 React.js 的组件父类 Component
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
欢迎进入React的世界
)
}
}
class Content extends Component {
render () {
return (
React.js是一个构建UI的库
)
}
}
/** 由于每个React组件只能有一个根节点,所以要渲染多个组件的时候,需要在最外层包一个容器,如果使用div, 会生成多余的一层dom
class App extends Component {
render () {
return (
)
}
}
**/
// 如果不想生成多余的一层dom可以使用React提供的Fragment组件在最外层进行包裹
class App extends Component {
render () {
return (
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
要明白JSX的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?
看下面的DOM结构
<div class='app' id='appRoot'>
<h1 class='title'>欢迎进入React的世界h1>
<p>
React.js 是一个帮助你构建页面 UI 的库
p>
div>
上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:
{
tag: 'div',
attrs: { className: 'app', id: 'appRoot'},
children: [
{
tag: 'h1',
attrs: { className: 'title' },
children: ['欢迎进入React的世界']
},
{
tag: 'p',
attrs: null,
children: ['React.js 是一个构建页面 UI 的库']
}
]
}
但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。
下面代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
欢迎进入React的世界
React.js 是一个构建页面 UI 的库
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
编译之后将得到这样的代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
React.createElement(
"div",
{
className: 'app',
id: 'appRoot'
},
React.createElement(
"h1",
{ className: 'title' },
"欢迎进入React的世界"
),
React.createElement(
"p",
null,
"React.js 是一个构建页面 UI 的库"
)
)
)
}
}
ReactDOM.render(
React.createElement(App),
document.getElementById('root')
)
React.createElement
会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等, 语法为
React.createElement(
type,
[props],
[...children]
)
所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:
JSX —使用react构造组件,bable进行编译—> JavaScript对象 —
ReactDOM.render()
—>DOM元素 —>插入页面
想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:
// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
Hello world
行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如render
函数里、组件原型上、外链js文件中
class
React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,class
需要写成className
(因为毕竟是在写类js代码,会收到js规则的现在,而class
是关键字)
Hello world
props
是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
:
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
欢迎进入{this.props.name}的世界
)
}
}
const Content = (props) => {
return (
{props.name}是一个构建UI的库
)
}
class App extends Component {
render () {
return (
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render () {
return (
欢迎进入{this.props.name}的世界
)
}
}
const Content = (props) => {
return (
{props.name}是一个构建UI的库
)
}
// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
name: 'React.js'
}
class App extends Component {
render () {
return (
{/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children
。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
欢迎进入{this.props.children}的世界
)
}
}
const Content = (props) => {
return (
{props.children}
)
}
class App extends Component {
render () {
return (
React
React.js是一个构建UI的库
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
React其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的props
设定参数检查,需要安装和使用prop-types:
$ npm i prop-types -S
react 组件中可变的数据统一放在状态机中, 这个状态机就是state的值
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
第一种方式
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
state = {
name: 'React',
isLiked: false
}
render () {
return (
欢迎来到{this.state.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
另一种方式(推荐)
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
render () {
return (
欢迎来到{this.state.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
this.props
和this.state
是纯js对象,在vue中,data属性是利用Object.defineProperty
处理过的,更改data的数据的时候会触发数据的getter
和setter
,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState
。
isLiked
存放在实例的 state
对象当中,组件的 render
函数内,会根据组件的 state
的中的isLiked
不同显示“取消”或“收藏”内容。下面给 button
加上了点击的事件监听。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
handleBtnClick = () => {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
欢迎来到{this.state.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
setState
有两个参数
第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做updater
第二个参数,是一个回调函数
参数是对象
this.setState({
isLiked: !this.state.isLiked
})
参数是方法 ,可以获取上一次的state的状态值
this.setState((prevState, props) => {
return {
isLiked: !prevState.isLiked
}
})
注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props
所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState((prevState, props) => {
return {
isLiked: !prevState.isLiked
}
}, () => {
console.log('回调里的',this.state.isLiked)
})
console.log('setState外部的',this.state.isLiked)
总结:
1.setState
更改state的值,有两个参数,第一个参数可以是对象或函数,第二个参数回调函数
2.对于第一个参数中的函数格式,可以获取到最新的state的值,适合于对state的设置依赖于之前的值的这种情况
3.第二个参数回调函数 可以获取到最新的state的值,适合于想要在合成函数中立即获取最新的状态值情况
4.setState
是异步的,并不是本身的函数是异步的,setState
函数本身是同步的,只不过,执行的顺序不同导致感受上是异步的。setState
设置允许批量更新(合成事件、钩子函数中),看起来像是异步的。
5.异步表现在合成事件和钩子函数上,会批量更新;在setTimeout,原生事件上表现出同步特性,也不会批量更新
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过 this.setState
方法进行更新,setState
会导致组件的重新渲染。
props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props
,否则组件的 props
永远保持不变。
如果搞不清 state
和 props
的使用场景,记住一个简单的规则:尽量少地用 state
,多用 props
。
没有 state
的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
总结:props 传参,不能更改;state 组件自己的状态值,能更改
如果有多个组件(兄弟节点)共享一个数据,把这个数据放到共同的父级组件中来管理 像vue中的bus
需求:有一个文本框的组件,使用了两次以上,在一个文本框中输入内容的时候,另一个文本框会跟着发生变化
import React, { Component } from "react";
import ReactDOM from "react-dom";
// 需求:有一个文本框的组件,使用了两次以上,在一个文本框中输入内容的时候,另一个文本框会跟着发生变化
//Input 组件
//想要让两个文本框有关系,需要state在父组件中统一管理,这种方式叫做状态提升
//因为state统一放在父组件中,将 state的值以参数的形式传给子组件,并且传递了父组件的函数到子组件
class Input extends Component {
constructor(props) {
super(props);
this.state = {
content: ""
}
}
//接收文本框的值
changeValue = (ev) => {
//调用了父组件的函数
this.props.onContentChange(ev.target.value)
}
render() {
return (
{/* 受控组件,受state控制的组件 */}
);
}
}
class App extends Component {
constructor() {
super();
this.state = { content: "" }
}
handleContentChange = (newContent) => {
this.setState({
content: newContent
})
}
render() {
const { content } = this.state;
return
第一个文本框:
第二个文本框:
}
}
ReactDOM.render(
,
document.getElementById('root')
)
React组件的数据渲染是否被调用者传递的props
完全控制,控制则为受控组件,否则非受控组件。
条件渲染
三目运算符
{
condition ? '❤️取消' : '收藏'
}
if else 进行条件渲染
class App extends Component {
constructor(props) {
super(props);
this.state = { likeType: true }
}
render() {
let str = "";
if (this.state.likeType) {
str = "喜欢";
} else {
str = "不喜欢"
}
return (
范冰冰{str} 李晨
);
}
}
if else 渲染整个的return
class App extends Component {
constructor(props) {
super(props);
this.state = { likeType: true }
}
render() {
if (this.state.likeType) {
return (
范冰冰喜欢 李晨
);
} else {
return (
范冰冰不喜欢李晨
);
}
}
}
列表渲染
forEach map(优先考虑)
// 数据
const people = [{
id: 1,
name: 'Leo',
age: 35
}, {
id: 2,
name: 'XiaoMing',
age: 16
}]
// 渲染列表
{
people.map(person => {
return (
- {person.name}
- age: {person.age}
)
})
}
React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是key
,这个 key
必须是每个元素唯一的标识
虚拟 DOM Diff 算法解析
对于富文本创建的内容,相当于vue v-html,后台拿到的数据是这样的:
content = "React.js是一个构建UI的库
"
处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用dangerouslySetHTML
属性,它允许我们动态设置innerHTML
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
content : "React.js是一个构建UI的库
"
}
}
render () {
return (
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
直接在render里写行内的箭头函数(不推荐)
在组件内使用箭头函数定义一个方法(推荐)
class App extends Component {
constructor(props) {
super(props);
this.state = { num: 0 }
}
handleChangeNum = () => {
this.setState({ num: this.state.num + 1 })
}
render() {
return (
);
}
}
直接在组件内定义一个非箭头函数的方法,然后在render里直接使用onClick={this.handleClick.bind(this)}
(不推荐)
class App extends Component {
constructor(props) {
super(props);
this.state = { num: 0 }
}
handleChangeNum() {
this.setState({ num: this.state.num + 1 })
}
render() {
return (
);
}
}
直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(不推荐)
class App extends Component {
constructor(props) {
super(props);
this.state = { num: 0 }
//需要给 函数绑定this
this.handleChangeNum = this.handleChangeNum.bind(this);//不推荐
}
handleChangeNum() {
this.setState({ num: this.state.num + 1 })
}
render() {
return (
);
}
}
和普通浏览器一样,事件handler会被自动传入一个 event
对象,这个对象和普通的浏览器 event
对象所包含的方法和属性都基本一致。不同的是 React中的 event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
在render
里调用方法的地方外面包一层箭头函数 ***
在render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递
通过event
传递
比较推荐的是做一个子组件, 在父组件中定义方法,通过props
传递到子组件中,然后在子组件件通过this.props.method
来调用 状态提升
处理多个表单时,可以按照表单name来保存state的值,不需要写两个函数来处理
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
xing: '',
ming: ''
}
}
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render () {
const {
xing,
ming
} = this.state
return (
欢迎您: {xing}{ming}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:
<form>
<label>
名字:
<input type="text" name="name" />
label>
<input type="submit" value="提交" />
form>
此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。
在 HTML 中,表单元素(如、 和 )通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用setState()
来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange=(event)=> {
this.setState({value: event.target.value});
}
handleSubmit=(event)=> {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
由于在表单元素上设置了 value
属性,因此显示的值将始终为 this.state.value
,这使得 React 的 state 成为唯一数据源。由于 handlechange
在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。
对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。
在 HTML 中, 元素通过其子元素定义其文本:
而在 React 中, 使用
value
属性代替。这样,可以使得使用 的表单和使用单行 input 的表单非常类似:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
};
}
handleChange=(event)=> {
this.setState({value: event.target.value});
}
handleSubmit=(event)=> {
alert('提交的文章: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
请注意,this.state.value
初始化于构造函数中,因此文本区域默认有初值。
在 HTML 中, 创建下拉列表标签。例如,如下 HTML 创建了水果相关的下拉列表:
请注意,由于 selected
属性的缘故,椰子选项默认被选中。React 并不会使用 selected
属性,而是在根 select
标签上使用 value
属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
}
handleChange=(event)=> {
this.setState({value: event.target.value});
}
handleSubmit=(event)=> {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
总的来说,这使得, ,
和
之类的标签都非常相似—它们都接受一个
value
属性,你可以使用它来实现受控组件。
注意
你可以将数组传递到
value
属性中,以支持在select
标签中选择多个选项:
class MulFlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "coconut",
arr: [],
options: [
{ value: "grapefruit", label: "葡萄柚" },
{ value: "lime", label: "酸橙" },
{ value: "coconut", label: "椰子" },
{ value: "mango", label: "芒果" }
]
};
}
handleChange=(e)=>{
let idx = this.state.arr.findIndex(item=>{
return item === e.target.value
})
if (idx >= 0) {
this.state.arr.splice(idx,1);
} else {
this.state.arr.push(e.target.value);
}
let arr = this.state.arr;
this.setState({arr});
}
render() {
return (
<div>
<select multiple={true} value={this.state.arr} onChange={this.handleChange}>
{this.state.options.map((item,index) => {
return <option value={item.value} key={index}>{item.label}</option>;
})}
</select>
</div>
);
}
}
export default Test4;
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
}
handleInputChange=(event)=> {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
参与:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
来宾人数:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
在 HTML 中, 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。
<input type="file" />
因为它的 value 只读,所以它是 React 中的一个非受控组件。将与其他非受控组件在后续文档中一起讨论。
在受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefined 或 null。
<input value={null} />
在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。
1.和vue类似的旧式写法:this.refs.**
2.使用React.createRef(); React.setRef()的写法
例如,下面的代码使用非受控组件接受一个表单的值:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit=(event) =>{
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。 不推荐使用 ref,性能会差。
在 React 渲染生命周期时,表单元素上的 value
将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue
属性,而不是 value
。
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
同样, 和
支持
defaultChecked
, 和
支持
defaultValue
。
在 HTML 中, 可以让用户选择一个或多个文件上传到服务器,或者通过使用 File API 进行操作。
在 React 中, 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
您应该使用 File API 与文件进行交互。下面的例子做了图片的预览
import React, { Component } from 'react'
export default class App extends Component {
fileRef = React.createRef()
imgRef = React.createRef()
getData = () => {
const file = this.fileRef.current.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
const _this = this
// 注意this的指向
reader.onload = function () {
_this.imgRef.current.src = this.result
}
}
render() {
return (
<div>
<input type="file" ref={ this.fileRef }/>
<button onClick = { this.getData }>预览图片</button>
<img src="" ref={ this.imgRef } alt=""/>
</div>
)
}
}
组件化开发React todolist, 项目开发中的组件的基本目录结构基本上是这样的:
/your-project
- src
- …
- components
- YourComponentOne
- index.js/YourComponentOne.js
- YourComponentTwo
- index.js/YourComponentTwo.js
- index.js 用于导出组件
注意:一个组件只干一件事情 ,所以TodoList和Form要做成两个组件
React的生命周期从广义上分为三个阶段:挂载、渲染、卸载
因此可以把React的生命周期分为两类:挂载卸载过程和更新过程。
React的生命周期图:
// 旧版本
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
componentWillMount () {
// 没什么用 --- 在新版本中 --- 废弃
console.log('componentWillMount')
}
render () {
// 类组件必不可少的一个
console.log('render')
return (
{ this.state.count }
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 运行时阶段
componentWillReceiveProps () {
// 监听组件的数据的变化
// 子组件接收父组件的数据 新版已经淘汰
console.log('componentWillReceiveProps')
}
shouldComponentUpdate () {
// 提升性能的关键,如果不需要更新,返回false ***
// 要不不写要写必写返回值
// 父动子动 ==》 父动子不动-子中添加return false
console.log('shouldComponentUpdate')
// return true
return false
}
componentWillUpdate () {
// 没什么用
console.log('componentWillUpdate')
}
// render () {} 与 初始化相同
componentDidUpdate () {
// DOM操作,实例话的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
// 销毁
componentWillUnmount () {
// 类似于vue 的beforeDestory
console.log('componentDidUpdate')
}
// 错误
componentDidCatch () {
// 当前组件以及后代组件发生错误时触发该钩子
console.log('componentDidCatch')
}
}
componentDidMount 获取数据实例
1.安装 axios yarn add axios
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list:[]
}
}
//在这个钩子函数中可以获取数据
componentDidMount() {
axios.get("/ajax/movieOnInfoList?token=&optimus_uuid=BFF48470A1A911EBB47AF3106CA0923DD7EE65AD136748C79F02A6FBD790E154&optimus_risk_level=71&optimus_code=10").then(res => {
console.log(res);
this.state.list = res.data.movieList;
this.setState({})
})
}
render() {
return (
);
}
}
跨域配置:
2.在 package.json 中配置
“proxy”: “https://srv.hotkidceo.com/”,
获取数据时需要把域名删除
3.重启服务器
componentDidMount 实现 文字透明度的渐变效果
class App extends Component {
constructor(props) {
super(props);
this.state = {
opacity: 1 //不透明
}
}
//在这个钩子函数中可以获取数据
componentDidMount() {
setInterval(() => {
let myOpacity = this.state.opacity;
myOpacity -= 0.05;
if (myOpacity <= 0) {
myOpacity = 1;
}
//更改状态后需要设置 state
this.setState({
opacity: myOpacity
})
}, 500);
}
render() {
return (
hello world
);
}
}
shouldComponentUpdate 实例
//shouldComponentUpdate的实例
import React, { Component } from 'react'
//两个子组件
//注意: 父组件中 state的值发生变化时,子组件也跟着渲染了,这样性能差
//想要让 父组件中 state的值发生变化时,子组件 不渲染,需要使用 shouldComponentUpdate
class Show extends Component {
shouldComponentUpdate() {
return false;
}
render() {
console.log("Show 子组件渲染函数运行了");
return Show 组件
}
}
export default class ShouldEg extends Component {
constructor() {
super();
this.state = {
num: 0
}
}
change = () => {
this.setState({ num: this.state.num + 1 })
}
render() {
return (
shouldComponentUpdate的实例
{this.state.num}
)
}
}
** 常用钩子函数:
render(渲染钩子函数) 、
componentDidMount(挂载后的钩子函数,可以获取数据执行异步操作)、
componentDidUpdate (更新后的钩子函数)、
shouldComponentUpdate(确定子组件是否需要更新的,true 组件会更新,false 组件不会更新,一般用于性能优化,等同于 PureComponent)、
componentWillUnmount(卸载前的钩子函数,回收操作, 对绑定事件解除绑定,停止定时器,停止未完成的ajax请求、停止轮播图)
** 最新版的react 删除了:componentWillMount,componentWillReceiveProps,componentWillUpdate,
** 增加了:static getDerivedStateFromProps、 getSnapshotBeforeUpdate()
16.3版本生命周期图示:
16.4及以上版本生命周期图示:
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
//
static getDerivedStateFromProps () {
// 一般人用不到
// state 的值在任何时候都取决于 props
console.log('getDerivedStateFromProps')
return null
}
render () {
// 类组件必不可少的一个
console.log('render')
return (
{ this.state.count }
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 运行时阶段 通过props获取或更新state的值
// static getDerivedStateFromProps (){}
shouldComponentUpdate () {
// 提升性能的关键,如果不需要更新,返回false
// 要不不写要写必写返回值
// 父动子动 ==》 父动子不动-子中添加return false
console.log('shouldComponentUpdate')
// return true
return false
}
getSnapshotBeforeUpdate () {
// 一般人用不到
// 出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等
//在react `render()`后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
console.log('getSnapshotBeforeUpdate')
return 666;
}
// render () {} 与 初始化相同
componentDidUpdate (prevProps,prevState,snapshot) {
// DOM操作,实例化的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
// 销毁
componentWillUnmount () {
// 类似于vue 的beforeDestory
console.log('componentDidUpdate')
}
// 错误
componentDidCatch () {
// 当前组件以及后代组件发生错误时触发该钩子
console.log('componentDidCatch')
}
//
static getDerivedStateFromError () {
// 后代组件抛出错误后被调用
console.log('getDerivedStateFromError')
return null
}
}
常用的生命周期钩子
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
render () {
// 类组件必不可少的一个
console.log('render')
return (
{ this.state.count }
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 代替 旧版本中 componentWillReceiveProps 钩子
// render () {} 与 初始化相同
componentDidUpdate () {
// DOM操作,实例话的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
}
componentDidUpdate 实现文字渐变效果
class App extends Component {
constructor() {
super();
this.state = {
opacity: 1//不透明
}
}
componentDidMount() {
setInterval(() => {
//报错,this指向不对,目前指向了window,可以使用箭头函数
let myOpacity = this.state.opacity;
myOpacity -= 0.05;
//如果透明度到0,重新置1
if (myOpacity <= 0) {
myOpacity = 1;
}
// 将改变的值保存到 state
this.setState({
opacity: myOpacity
})
}, 100);
}
render() {
return (
hello world
)
}
}
componentDidUpdate 获取数据实例 引入axios ,获取候鸟数据,渲染
shouldComponentUpdate 实例
App.jsx
class App extends Component {
constructor(arg) {
super();
this.state = {
count: 0,
name: "lili"
}
}
setCount = () => {
this.setState({
count: this.state.count + 1
})
}
changeName = () => {
this.setState({
name: "huaer"
})
}
render() {
return (
点击按钮加一
{this.state.count}
)
}
}
Foo.jsx
class Foo extends Component {
/*
nextProps 将要传过来的props的值
nextState 将要传过来的state的值
shouldComponentUpdate 作用:阻止了未发生变化的组件的再次渲染,增加了性能
局限性:对于传递的参数是对象或数组时,判断不出来对象或数组的内部元素是否发生了变化
*/
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState);
if (nextProps.name === this.props.name) {
return false;
} else {
return true;
}
//return true;
}
render() {
console.log("foo组件的渲染运行了");
return (
Foo组件
)
}
}
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化(挂载)、运行中(更新)、销毁(卸载)、错误处理(异常)(16.3之后)
在组件初始化阶段会执行
constructor
static getDerivedStateFromProps()
componentWillMount() / UNSAFE_componentWillMount()
render()
componentDidMount()
props
或state
的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
(1) constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component
构造函数时,需要先在添加其他内容前,调用super(props)
,用来将父组件传来的props
绑定到这个类中,使用this.props
将会得到。
官方建议不要在constructor
引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()
。
constructor
中应当做些初始化的动作,如:初始化state
,将事件处理函数绑定到类实例上,但也不要使用setState()
。如果没有必要初始化state或绑定方法,则不需要构造constructor
,或者把这个组件换成纯函数写法。
当然也可以利用props
初始化state
,在之后修改state
不会对props
造成任何修改,但仍然建议大家提升状态到父组件中,或使用redux
统一进行状态管理。
constructor(props) {
super(props);
this.state = {
isLiked: props.isLiked
};
}
(2) static getDerivedStateFromProps(nextProps, prevState)
React 的 16.3 版本中对生命周期进行了较大的调整,这是为了开发者能正确地使用生命周期,避免误解其概念而造成反模式。
本节将重点介绍 getDerivedStateFromProps 这个生命周期。要注意的是,React 16.3 的版本中 getDerivedStateFromProps 的触发范围是和 16.4^ 是不同的,主要区别是在 setState
和 forceUpdate
时会不会触发,具体可以看这个生命全周期图 。
React v16.4后的getDerivedStateFromProps
*static getDerivedStateFromProps(props, state)* 在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
static静态方法只能Class(构造函数)来调用(App.staticMethod✅),而实例是不能的( (new App()).staticMethod ❌ );
当调用React Class组件时,该组件会实例化;
所以,React Class组件中,静态方法getDerivedStateFromProps无权访问Class实例的this,即this为undefined。
这里小结下 getDerivedStateFromProps 方法使用的注意点:
我们应该谨慎地使用 getDerivedStateFromProps 这个生命周期。使用时要注意下面几点:
(3) componentWillMount() / UNSAFE_componentWillMount()
componentWillMount()
将在React未来版本(官方说法 17.0)中被弃用。UNSAFE_componentWillMount()
在组件挂载前被调用,在这个方法中调用setState()
不会起作用,是由于他在render()
前被调用。
为了避免副作用和其他的订阅,官方都建议使用componentDidMount()
代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。
(4) render()
render()方法是必需的。当他被调用时,他将计算this.props
和this.state
,并返回以下一种类型:
当返回null
,false
,ReactDOM.findDOMNode(this)
将会返回null,什么都不会渲染。
render()
方法必须是一个纯函数,他不应该改变state
,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()
返回false
,render()
不会被调用。
(5) componentDidMount
componentDidMount
在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。
通常在这里进行ajax请求
如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
(6) componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)
官方建议使用getDerivedStateFromProps
函数代替componentWillReceiveProps
。当组件挂载后,接收到新的props
后会被调用。如果需要更新state
来响应props
的更改,则可以进行this.props
和nextProps
的比较,并在此方法中使用this.setState()
。
如果父组件会让这个组件重新渲染,即使props
没有改变,也会调用这个方法。
React不会在组件初始化props时调用这个方法。调用this.setState
也不会触发。
(7) shouldComponentUpdate(nextProps, nextState)
调用shouldComponentUpdate
使React知道,组件的输出是否受state
和props
的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。
在渲染新的props
或state
前,shouldComponentUpdate
会被调用。默认为true
。这个方法不会在初始化时被调用,也不会在forceUpdate()
时被调用。返回false
不会阻止子组件在state
更改时重新渲染。
如果shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不会被调用。
官方并不建议在
shouldComponentUpdate()
中进行深度查询或使用JSON.stringify()
,他效率非常低,并且损伤性能。
(8) UNSAFE_componentWillUpdate(nextProps, nextState)
在渲染新的state
或props
时,UNSAFE_componentWillUpdate
会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。
不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新state
或props
,调用getDerivedStateFromProps
。
(9) getSnapshotBeforeUpdate()
在react render()
后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
getSnapshotBeforeUpdate() {
let divWidth = this.divRef.current.getBoundingClientRect().width
return divWidth
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot)
}
(10) componentDidUpdate(prevProps, prevState, snapshot)
在更新发生后立即调用componentDidUpdate()
。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。
如果组件实现getSnapshotBeforeUpdate()
生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()
。否则,这个参数是undefined
。
(11) componentWillUnmount()
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount
中创建的任何监听。
(12) componentDidCatch(error, info)
错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。
如果类组件定义了此生命周期方法,则它将成错误边界。在它中调用setState()
可以让你在下面的树中捕获未处理的JavaScript错误,并显示一个后备UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
// `PureComponnet`里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,
// 就不会去重新render了
// PureComponent 与 Component 的区别
// 如果使用了 PureComponent ,还使用了shouldComponentUpdate ,后者的优先级高
export default class App extends PureComponent {
render() {
return (
)
}
}
ref
属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()
返回的组件实例,ref
可以挂载到组件上也可以是dom元素上class
声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:在React 最新的版本中,要使用ref
, 需要使用React.createRef
方法先生成一个ref
。
import React, { PureComponent } from 'react'
class Child extends PureComponent {
state = { aa: 100 }
fn = () => {
console.log(this.state.aa)
}
render () {
return (
Child
)
}
}
export default class App extends PureComponent {
testRef = React.createRef()
render() {
return (
)
}
componentDidMount () {
console.log(this.testRef.current.state.aa) // 100
this.testRef.current.fn() // 100
}
}
父传子 props
子传父 从父组件传 函数到子组件 ,子组件调用,传一个参数回父组件
兄弟 状态提升 (将 兄弟的节点的state状态值,统一交由父节点管理)
子孙 context Provider 发送数据 Consumer 接收数据 * redux 源码
ref 可以 传参,但是不常用 ,因为获取的是真实dom ,性能差些
redux react的统一资源状态管理器
父组件将自己的状态传递给子组件,子组件当做属性来接收props,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
复习
在父组件调用子组件的地方,添加一个自定义的属性
属性的值是一个变量,boolean类型或者是number类型
需要使用{}包裹数据
在子组件中可以通过 this.props.自定义的属性名 或者 props.自定义属性名渲染数据
如果需要指定 自定义属性名的 默认值,
如果是类组件,通过在组件内部使用 static defaultProps = { key: value}实现
如果是函数是组件,定义好组件以后,通过 组件.defaultProps={key:vlaue}实现
如果需要设定自定义属性名的数据类型,需要安装 模块 prop-types
如何验证:
组件.propTypes = { key: PropTypes.数据类型}
父组件利用ref
对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法.
上面的实例
this.props
接收到父组件的方法后调用。父给子传 值。----- 父传子
父给子传 函数。 ---- 子传父
在父组件调用子组件的地方,添加一个自定义的属性,该属性的值是一个函数
这个函数的参数就是子组件需要传递给父组件的值
在子组件中某一个事件内部,通过this.props.自定义属性名(参数)
完成传值
import React, { PureComponent } from 'react'
class Child extends PureComponent {
render () {
return (
Child
)
}
}
export default class App extends PureComponent {
getData (data) {
console.log(data)
}
render() {
return (
)
}
}
import React, { Component } from "react";
import ReactDOM from "react-dom";
// 需求:有一个文本框的组件,使用了两次以上,在一个文本框中输入内容的时候,另一个文本框会跟着发生变化
//Input 组件
//想要让两个文本框有关系,需要state在父组件中统一管理,这种方式叫做状态提升
//因为state统一放在父组件中,将 state的值以参数的形式传给子组件,并且传递了父组件的函数到子组件
class Input extends Component {
constructor(props) {
super(props);
this.state = {
content: ""
}
}
//接收文本框的值
changeValue = (ev) => {
//调用了父组件的函数
this.props.onContentChange(ev.target.value)
}
render() {
return (
<div>
{/* 受控组件,受state控制的组件 */}
<input type="text" value={this.props.content} onChange={this.changeValue} />
</div>
);
}
}
class App extends Component {
constructor() {
super();
this.state = { content: "" }
}
handleContentChange = (newContent) => {
this.setState({
content: newContent
})
}
render() {
const { content } = this.state;
return <div>
第一个文本框:<Input content={content} onContentChange={this.handleContentChange} /> <br />
第二个文本框:<Input content={content} onContentChange={this.handleContentChange} />
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context
api来实现跨组件通信, React 16.3之后的context
api较之前的好用。
my-context.js
//类似于vue的Provide/inject
import { createContext } from "react";
const myContext = createContext();
console.log(myContext);
export default myContext;
App.jsx
import myContext from './my-context';
class ChildA extends Component {
static contextType = myContext;
render() {
console.log(this);
return (A组件{this.context});
}
}
class ChildB extends Component {
render() {
return (B组件
);
}
}
//函数式组件中没有this,所以我们按下面的方式写
const ChildC = function (props) {
return (
ChildC
{
{
(value) => {value}
}
}
)
}
const ChildD = function (props) {
return (
ChildD
)
}
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
);
}
}
复杂的非父子组件通信在react中很难处理,多组件间的数据共享也不好处理,在实际的工作中我们会使用flux、redux、mobx来实现
使用 context实现 购物车的加一,减一(作业) ,
思路:
1.组件先写好,父子级关系定好
2.写count.js 创建 context 对象, 设置 count的值
3.在 根节点 引入 CountProvider,将子级孙子级的数据做到统一管理
5.写count.js 写个加一的函数increment,value 输出
6.在 ChildHood引入CountConsumer ,获取函数increment ,做加一操作
App.jsx
import React, { Component } from 'react';
import Child from './Child';
import { CountProvider } from './count'
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
);
}
}
export default App;
Child.jsx
import React, { Component } from 'react';
import ChildHood from './ChildHood';
//引入 consumer
import { CountConsumer } from './count';
class Child extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
子组件
{
{
({ count }) => {count}
}
}
);
}
}
export default Child;
ChildHood.jsx
import React, { Component } from 'react';
import { CountComsumer, CountConsumer } from './count';
class ChildHood extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
孙子组件
{
{
({ count, increment }) =>
{count}
}
}
);
}
}
export default ChildHood;
count.js
//context
import React, { Component, createContext } from 'react';
//创建 context 对象
const { Provider, Consumer: CountConsumer } = createContext();
//组件
class CountProvider extends Component {
constructor(props) {
super(props);
//统一管理的状态值
this.state = { count: 10 }
}
//加一
increment = (num) => {
this.setState(state => {
return {
count: state.count + num
}
})
}
//减一
render() {
return (
{this.props.children}
);
}
}
export { CountProvider, CountConsumer };
Higher-Order Components就是一个函数,传给它一个组件(参数是组件),它返回一个新的组件(返回值是组件)。作用:对原有的组件的扩展
const NewComponent = higherOrderComponent(YourComponent)
比如,我们想要我们的组件通过自动注入一个版权信息。
// withCopyright.js 定义一个高阶组件
import React, { Component, Fragment } from 'react'
// WrappedComponent 组件
const withCopyright = (WrappedComponent) => {
//return 新组件
return class NewComponent extends Component {
render() {
return (
©版权所有
)
}
}
export default withCopyright
// App.jsx 使用方式
import withCopyright from './withCopyright'
class App extends Component {
render () {
return (
Awesome React
React.js是一个构建用户界面的库
)
}
}
const CopyrightApp = withCopyright(App)
index.js 在App调用上传了参数
ReactDOM.render(<App msg="hi"></App>, document.getElementById('root'))
这样只要我们有需要用到版权信息的组件,都可以直接使用withCopyright这个高阶组件包裹即可。
高阶组件:是一个函数,参数是一个组件,返回值是一个组件。
高阶组件在项目当中经常使用的方式:
1.进行权限判断
页面中的按钮权限,在高阶组件中实现权限判断,页面权限判断,进行路由跳转
2.日志记录
逻辑提取出来,封装成高阶组件,如果哪个组件需要日志处理,在组件名的外面包裹一层日志高阶组件
3.数据校验
多个地方都需要校验数据,将逻辑拆出来,封装成高阶组件
4.异常处理
进行封装异常处理,例如,异常时来个弹窗报错
高阶组件实例:权限判断,需求:一个 admin(管理员) ,一个user(普通用户),
需求:管理员进页面、按钮就可见,普通用户进页面,按钮看不见
需求:管理员进页面、按钮就可见,普通用户进页面,按钮可见,置灰
需求:管理员进页面、按钮就可见,普通用户进页面,按钮可见,提示:目前没有权限,需要在小K申请权限
使用高阶组件解决这种需求经常变化,而且多个地方都需要鉴权的情况
高阶组件特点:
1.高阶组件是把某个组件装饰成一个新的组件,对功能进行了扩展
2.高阶组件进行了代码复用,封装,有利于解耦,增强了灵活性
3.核心思想 装饰器模式
在这里要讲解在CRA 中配置装饰器模式的支持。
装饰器是es7的新语法,装饰器可以对 类、方法、属性进行包装,包装后就在原有的基础上增加了一些新特性
高阶组件,装饰的对象是一个类
Portal 大门 门户, Portal Website 门户网站
Portal 传送门
render到一个组件中时,实际是改变了网页上的另一处的dom结构 ,一般使用在对话框、提示框
Portals 提供了一个最好的在父组件包含的DOM结构层级外的DOM节点渲染组件的方法。
ReactDOM.createPortal(child,container);
第一个参数child是可渲染的react子项,比如元素,字符串或者片段等。第二个参数container是一个DOM元素。
普通的组件,子组件的元素将挂载到父组件的DOM节点中。
render() {
// React 挂载一个div节点,并将子元素渲染在节点中
return (
<div>
{this.props.children}
</div>
);
}
有时需要将元素渲染到DOM中的不同位置上去,这是就用到的portal的方法。
render(){
// 此时React不再创建div节点,而是将子元素渲染到Dom节点上。domNode,是一个有效的任意位置的dom节点。
return ReactDOM.createPortal(
this.props.children,
domNode
)
}
一个典型的用法就是当父组件的dom元素有 overflow:hidden
或者z-inde
样式,而你又需要显示的子元素超出父元素的盒子。举例来说,如对话框,悬浮框,和小提示。
虽然通过portal渲染的元素在父组件的盒子之外,但是渲染的dom节点仍在React的元素树上,在那个dom元素上的点击事件仍然能在dom树中监听到。
App.jsx
import React, { Component } from 'react'
import Modal from './Modal';
export default class App extends Component {
state = {
count: 0
}
handleClick = () => {
console.log(0);
this.setState(state => ({
count: state.count + 1
}));
}
render() {
return (
hello
num: {this.state.count}
)
}
}
Modal.jsx
import React, { Component } from 'react'
import withPortal from './withPortal';
import "./style.css";
class Modal extends Component {
render() {
return (
Portal header
)
}
}
export default withPortal(Modal);
withPortal.js
import React, { Component } from 'react';
import { createPortal } from 'react-dom';
function withPortal(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
}
render() {
//参数,1要渲染的内容,2挂载的地方
return createPortal( , document.querySelector('body'))
}
}
}
export default withPortal;
style.css
div.portal{
position: absolute;
left:0;
top:0;
bottom:0;
right:0;
background:rgba(0, 0, 0, 0.5);
z-index:1000;
}
路由懒加载、
React.lazy
函数能让你像渲染常规组件一样处理动态引入(的组件)。
什么意思呢?其实就是懒加载。其原理就是利用es6 import()
函数。这个import
不是import命令
。同样是引入模块,import命令
是同步引入模块,而import()
函数动态引入。
当 Webpack 解析到该语法时,它会自动地开始进行代码分割(Code Splitting),分割成一个文件,当使用到这个文件的时候会这段代码才会被异步加载。
当你的程序越来越大,代码量越来越多。一个页面上堆积了很多功能,也许有些功能很可能都用不到,但是一样下载加载到页面上,所以这里面肯定有优化空间。就如图片懒加载的理论。
javascript
math.js
const add = (x, y) => {
return x + y;
}
export default add;
//import 命令
import { add } from './math';
console.log(add(16, 26));
//import函数
import("./math").then(math => {
console.log(math.add(16, 26));
});
动态
import()
语法目前只是一个 ECMAScript (JavaScript) 提案, 而不是正式的语法标准。预计在不远的将来就会被正式接受。http://es6.ruanyifeng.com/#docs/module#import
下面是import一个示例:
在test文件夹下新建两个文件
图片1:
test.html代码如下:
<div id="root">
页面无内容
div>
<button id="btn">加载jsbutton>
<script>
document.getElementById('btn').onclick=function(){
import('./test.js').then(d=>{
d.test()
})
}
script>
test.js代码如下:
function test(){
document.getElementById('root')
root.innerHTML='页面变的有内容了'
}
export {test}
图片2
这时候打开web服务让页面以http的方式访问,http://192.168.1.2:8080/test.html
我们在chrome的开发者工具下的Network可以看到只请求了一个页面。
图片3
但是当我们点击加载js,你会发现test.js会以动态的方式加入到代码中,同时执行了test函数,使页面的内容发生了变化。
图片4
在React.lazy
和常用的三方包react-loadable
,都是使用了这个原理,然后配合webpack进行代码打包拆分达到异步加载,这样首屏渲染的速度将大大的提高。
由于React.lazy
不支持服务端渲染,所以这时候react-loadable
就是不错的选择。
下面示例代码使用create-react-app脚手架搭建:
//OtherComponent.js 文件内容
import React from 'react'
const OtherComponent = ()=>{
return (
我已加载
)
}
export default OtherComponent
// App.js 文件内容
import React from 'react';
import './App.css';
//使用React.lazy导入OtherComponent组件
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function App() {
return (
);
}
export default App;
这是最简单的React.lazy
,但是这样页面会报错。这个报错提示我们,在React使用了lazy
之后,会存在一个加载中的空档期,React不知道在这个空档期中该显示什么内容,所以需要我们指定。接下来就要使用到Suspense
。
图片5
如果在 App
渲染完成后,包含 OtherComponent
的模块还没有被加载完成,我们可以使用加载指示器为此组件做优雅降级。这里我们使用 Suspense
组件来解决。
这里将App
组件改一改
import React, { Suspense, Component } from 'react';
import './App.css';
//使用React.lazy导入OtherComponent组件
const OtherComponent = React.lazy(() => import('./OtherComponent'));
export default class App extends Component {
state = {
visible: false
}
render() {
return (
Loading... }>
{
this.state.visible
?
:
null
}
我们指定了空档期使用Loading展示在界面上面,等OtherComponent
组件异步加载完毕,把OtherComponent
组件的内容替换掉Loading上。
图片6
图片7
为了演示我把chrome网络调到lower-end mobile
,不然看不到loading出现。
可以从上面图片看出,当点击加载的时候,页面的head会插入``这段代码,发出一个get请求,页面开始显示loading,去请求2.chunk.js
文件。
请求结束返回内容就是OtherComponent
组件的内容,只是文件名称和文件内容经过webpack处理过。
注意:
Suspense
使用的时候,fallback
一定是存在且有内容的, 否则会报错。
React Router现在的版本是5, 于2019年3月21日搞笑的发布,搞笑的官网链接, 本来是要发布4.4的版本的,结果成了5。从4开始,使用方式相对于之前版本的思想有所不同。之前版本的思想是传统的思想:路由应该统一在一处渲染, Router 4之后是这样的思想:一切皆组件
React Router包含了四个包:
包名 | Description |
---|---|
react-router |
React Router核心api |
react-router-dom |
React Router的DOM绑定,在浏览器中运行不需要额外安装react-router |
react-router-native |
React Native 中使用,而实际的应用中,其实不会使用这个。 |
react-router-config |
静态路由的配置 |
主要使用react-router-dom
正常情况下,直接按照官网的demo就理解 路由的使用方式,有几个点需要特别的强调:
exact
属性标识是否为严格匹配, 为true
是表示严格匹配,为false
时为正常匹配。
怎么在渲染组件的时候,对组件传递属性呢?使用component
的方式是不能直接在组件上添加属性的。所以,React Router的Route
组件提供了另一种渲染组件的方式 render
, 这个常用于页面组件级别的权限管理。
路由的参数传递与获取
Switch组件
总是渲染第一个匹配到的组件
React Router甚至大部分的前端路由都是依赖于history.js
的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。
hash
来存储在不同状态下的history
信息,对应createHashHistory
,通过检测location.hash
的值的变化,使用location.replace
方法来实现url跳转。通过注册监听window
对象上的hashChange
事件来监听路由的变化,实现历史记录的回退。createBrowserHistory
, 使用包括pushState
, replaceState
方法来进行跳转。通过注册监听window
对象上的popstate
事件来监听路由的变化,实现历史记录的回退。createMemoryHistory
。直接在内存里push
和pop
状态。安装依赖
yarn react-router-dom
cnpm i -S react-router-dom
引入
import { HashRouter as Router, Route, Link } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter as Router, Route, Link } from 'react-router-dom';
const Home = () => (<div>首页</div>)
const About = () => (<div>关于我们</div>)
const App = () => (
<Router>
<div>
<Link to={"/"}>首页</Link>
<Link to={"/about"}>关于我们</Link>
</div>
{/* exact={true} 只完全匹配/ */}
<div>
<Route path={"/"} component={Home} />
<Route path={"/about"} component={About} />
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
BrowserRouter | HashRouter,前者不带#,后者带#
如:HashRouter => http://localhost:3000/#/about
BrowserRouter => http://localhost:3000/about
NavLink | Link, 前者可以设置用户的选中样式,后者不可以 ---- 用来做声明式跳转
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (<div>首页</div>)
const About = () => (<div>关于我们</div>)
const App = () => (
<Router>
<div>
<NavLink to={"/"} exact={true} >首页</NavLink>
<NavLink to={"/about"}>关于我们</NavLink>
</div>
{/* 只完全匹配/ */}
<div>
<Route path={"/"} exact={true} component={Home} />
<Route path={"/about"} component={About} />
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Route 设置 路由对应的组件有 3种形式 component | children | render
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (<div>首页</div>)
const About = () => (<div>关于我们</div>)
const App = () => (
<Router>
<div>
<NavLink to={"/"} exact={true} >首页</NavLink>
<NavLink to={"/about"}>关于我们</NavLink>
<NavLink to={"/render"}>render页面</NavLink>
<NavLink to={"/child"}>children页面</NavLink>
</div>
{/* 只完全匹配/ */}
<div>
<Route path={"/"} exact={true} component={Home} />
<Route path={"/about"} component={About} />
<Route path="/render" render={() => <div>render页面</div>} />
{/* children 比较特殊,每个页面都会匹配上 */}
<Route path="/child" children={() => <div>children页面</div>} />
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
三种方式
location 中的
search ? key=value
state 可以传递 对象
match 中的 params
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (<div>首页</div>)
const About = (props) => (<div>关于我们{props.match.params.userid}</div>)
class Cart extends Component {
render() {
console.log(this.props);
return (<div>购物车页面
用户id:{new URLSearchParams(this.props.location.search).get('id')}
用户名:{new URLSearchParams(this.props.location.search).get('name')}
</div>);
}
}
class State extends Component {
render() {
console.log(this.props);
return (<div>
state页面
{this.props.location.state.username}
</div>);
}
}
/*
props中有
history go(-1) push()
location search state
match params
*/
const App = () => (
<Router>
<div>
<NavLink to={"/"} exact={true} >首页</NavLink>
<NavLink to={"/about/666"}>关于我们</NavLink>
<NavLink to={{ pathname: "/cart", search: "?id=123&name=lili" }}>购物车页面</NavLink>
<NavLink to={{ pathname: "/state", state: { "username": "山海经" } }}>state页面</NavLink>
</div>
{/* 只完全匹配/ */}
<div>
<Route path={"/"} exact={true} component={Home} />
<Route path={"/about/:userid"} component={About} />
<Route path={"/cart"} component={Cart} />
<Route path={"/state"} component={State} />
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
goHome = () => {
this.props.history.push('/');
}
goBack = () => {
this.props.history.go(-1);
}
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (首页)
const About = (props) => (关于我们{props.match.params.userid})
class Cart extends Component {
render() {
console.log(this.props);
return (购物车页面);
}
}
const App = () => (
首页
关于我们
购物车页面
{/* 只完全匹配/ */}
404页面} />
render页面} />
)
ReactDOM.render(
,
document.getElementById('root')
);
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch, Redirect, Prompt } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (<div>首页</div>)
const About = (props) => (<div>关于我们{props.match.params.userid}</div>)
class Center extends Component {
render() {
return (loginType ? <div>用户中心</div> : <Redirect to="/login" />);
}
}
const Login = (props) => (<div>登录</div>)
class Cart extends Component {
render() {
console.log(this.props);
return (<div>购物车页面
<Prompt when={true} message={"您确定要离开这个页面么?"} />
</div>);
}
}
let loginType = false;
const App = () => (
<Router>
<div>
<Link to={"/"} exact={true} >首页</Link>
<Link to={"/about/666"}>关于我们</Link>
<Link to={{ pathname: "/cart" }}>购物车页面</Link>
<Link to={"/center"} >用户中心</Link>
<Link to={"/login"} >登录</Link>
</div>
<div>
<Switch>
<Route path={"/"} exact={true} component={Home} />
<Route path={"/about/:userid"} component={About} />
<Route path={"/cart"} component={Cart} />
<Route path={"/center"} component={Center} />
<Route path={"/login"} component={Login} />
<Redirect from="/*" to="/" />
{/* 404页面定位到首页 */}
</Switch>
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import "./index.css";
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (首页)
const About = () => (关于我们)
const Login = () => (登录)
//需求:center页面中有两个子页面
class Center extends Component {
render() {
return 用户中心
子页一
子页二
;
}
}
const Child1 = () => (子页1)
const Child2 = () => (子页2)
const App = () => (
首页
关于我们
用户中心
登录
)
ReactDOM.render(
,
document.getElementById('root')
);
作者: 二传二改请声明