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的方法其实很简单——分片。把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
我们以前操作dom的方式是通过document.getElementById()的方式,这样的过程实际上是先去读取html的dom结构,将结构转换成变量,再进行操作
而reactjs定义了一套变量形式的dom模型,一切操作和换算直接在变量中,这样减少了操作真实dom,性能真实相当的高,和主流MVC框架有本质的区别,并不和dom打交道
react最核心的思想是将页面中任何一个区域或者元素都可以看做一个组件 component
那么什么是组件呢?
组件指的就是同时包含了html、css、js、image元素的聚合体
使用react开发的核心就是将页面拆分成若干个组件,并且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式
其实reactjs的核心内容就是数据绑定,所谓数据绑定指的是只要将一些服务端的数据和前端页面绑定好,开发者只关注实现业务就行了
在vue中,我们使用render函数来构建组件的dom结构性能较高,因为省去了查找和编译模板的过程,但是在render中利用createElement创建结构的时候代码可读性较低,较为复杂,此时可以利用jsx语法来在render中创建dom,解决这个问题,但是前提是需要使用工具来编译jsx
#create-react-app
全局安装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. 安装过程较慢,可以推荐学员使用yarn
Installing react, react-dom, and react-scripts...
如果不想全局安装,可以直接使用npx
$ npx create-react-app your-app 也可以实现相同的效果
这需要等待一段时间,这个过程实际上会安装三个东西
出现下面的界面,表示创建项目成功:
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 install命令
npm cache clean --force
之后再执行npm install
命令//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
hello world
,
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
const app = hello world
ReactDOM.render(
app,
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
const app =(props)=> 欢迎进入{props.name}的世界
ReactDOM.render(
app({
name:'react'
}),
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
//组件名称必须大写
const App =(props)=> 欢迎进入{props.name}的世界
ReactDOM.render(
,
document.getElementById('root')
);
ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class
是目前官方推荐的使用方式
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
class App extends React.Component {
//必须要实现
render() {
return (
欢迎来到{this.props.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
);
在16以前的版本还支持这样创建组件, 但现在的项目基本上不用
yarn add create-react-class -S
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
import createClass from 'create-react-class';
var Hello = createClass({
render(){
return gp18
}
})
ReactDOM.render(
,
document.getElementById('root')
);
import React,{Fragment} from 'react'
import Hotshowing from './Hotshowing'
import Comingsoon from './Comingsoon'
export default class Movie extends React.Component {
render() {
return (
// Fragment
<>
>
)
}
}
//Comingsoon.jsx
import React, { Component } from 'react';
class Comingsoon extends Component {
render() {
return (
coming soon page
);
}
}
export default Comingsoon;
//Hotshowing.jsx
import React, { Component } from 'react';
class Hotshowing extends Component {
render() {
return (
hotshowing page
);
}
}
export default Hotshowing;
#JSX 原理
基础:https://react.docschina.org/docs/introducing-jsx.html
import React, { Component } from 'react';
class Login extends Component {
fullname(firstname, lastname) {
return firstname + lastname;
}
render() {
let islogin = sessionStorage.getItem('username')
if (islogin) {
let username = sessionStorage.getItem('username');
return (
{1 + 2 + 3}
欢迎, {username}
{
this.fullname('han','ye')
}
);
} else {
return 您还没有登录
}
}
}
export default Login;
要明白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样式
import React, { Component } from 'react';
import "./Movie.css"
import common from "./common.module.css"
class Movie extends Component {
constructor(){
super();
this.state ={
styleObj:{
fontSize:'50px'
}
}
}
render() {
return (
// 使用class
{/* 行内样式 */}
正在热映
即将上映
{/* module css */}
异常信息
);
}
}
export default Movie;
classnames: https://www.npmjs.com/package/classnames
npm install classnames --save
import React, { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './styles.css'
let cx = classNames.bind(styles);
class Profile extends Component {
render() {
let names = cx({
inProcess:true,
error:this.props.error
})
return (
gp18
);
}
}
export default Profile;
styled-components`是针对React写的一套css-in-js框架,简单来讲就是在js中写css
yarn add styled-components -S
import React from 'react'
import styled from 'styled-components'
const Container = styled.div`
width:500px;
height:500px;
background:${(props)=>props.color};
font-size:30px;
h1 {
font-size:50px;
}
`
export {
Container
}
import React, { Component } from 'react';
import {Container} from './Search_style'
class Search extends Component {
render() {
return (
gp18
hello world
);
}
}
export default Search;
#案例-TodoList
props
是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
###设置组件的默认props
import React, { Component } from 'react';
class Title extends Component {
//第一种定义方式
static defaultProps = {
name:"react"
}
render() {
return (
欢迎进入{this.props.name} 的世界
);
}
}
//第二种方式
// Title.defaultProps = {
// name:'react'
// }
export default Title;
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children
。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。
ReactDOM.render(
gp18
react
,
document.getElementById('root')
);
import React, { Component } from 'react';
class Content extends Component {
render() {
return (
{
this.props.children
}
);
}
}
export default Content;
import React, { Component } from 'react';
class Title extends Component {
render() {
return (
欢迎进入{this.props.children} 的世界
);
}
}
export default Title;
React其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的props
设定参数检查,需要安装和使用prop-types:
$ npm i prop-types -S
import React, { Component } from 'react';
import Proptypes from 'prop-types'
class Product extends Component {
render() {
return (
产品名称: {this.props.name}
);
}
}
Product.propTypes={
name:Proptypes.string,
//['北京','天津']
// city:Proptypes.arrayOf(Proptypes.string).isRequired,
// customProp:function(props,PropName){
// if(!/gp/.test(props[PropName])){
// return new Error('内容非法')
// }
// console.log(arguments)
// },
customArrayProp: Proptypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/北京/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
}).isRequired
}
export default Product;
##状态(state)
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
###定义state
constructor() {
super();
//state定义的第一种方式
//推荐
// this.state = {
// count: 1,
// title: "中国机长"
// }
}
//state定义的第二种方式
state ={
title:"中国机长",
count:1
}
###setState
import React, { Component } from 'react';
class Movie extends Component {
constructor() {
super();
//state定义的第一种方式
// this.state = {
// count: 1,
// title: "中国机长"
// }
}
//state定义的第二种方式
state ={
title:"中国机长",
count:1
}
componentDidMount() {
// setTimeout(() => {
//修改state的第一种方式
// this.setState({
// title: "战狼2"
// })
// }, 2000)
//修改state的第二种方式
// this.state.title ="战狼2"
// setTimeout(()=>{
// this.setState({})
// },2000)
//修改state的数据是异步的
// this.setState({
// count: this.state.count + 1
// })
// console.log(this.state.count)
//修改state的第三种方式
this.setState((preState, props) => {
console.log(props);
//数据的更新,会做merge
return {
count: preState.count + 1
}
}, () => {
console.log(this.state.count)
})
setTimeout(()=>{
this.setState({
count:3
})
console.log(this.state.title,this.props.city)
},3000)
}
render() {
console.log(1)
return (
{this.state.title}
);
}
}
export default Movie;
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过 this.setState
方法进行更新,setState
会导致组件的重新渲染。
props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props
,否则组件的 props
永远保持不变。
如果搞不清 state
和 props
的使用场景,记住一个简单的规则:尽量少地用 state
,多用 props
。
没有 state
的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
##状态提升
如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
条件渲染
列表渲染
import React, { Component } from 'react';
import Comp1 from './Com1';
import Comp2 from './Com2'
class People extends Component {
state = {
condition: false,
list: [{
id: 1,
name: 'hanye',
age: 20
}, {
id: 2,
name: "周光磊",
age: 18
}]
}
render() {
return (
<>
{
this.state.condition ? :
}
{
this.state.list.map((person) => {
return (- name:{person.name}-age:{person.age}
)
})
}
>
);
}
}
export default People;
Counter.jsx
import React, { Component } from 'react';
import Button from './Button'
import Count from './Count'
import styled from 'styled-components'
const CountWrapper = styled.div`
h1 {
display:block;
color:red;
}
button {
width:100px;
height:40px;
background:blue;
color:white;
font-size:20px;
}
`
class Counter extends Component {
constructor() {
super();
this.state = {
count: 0
}
}
handleChange(type) {
console.log('handle change')
this.setState((preState, props) => {
if (type === 'increment') {
return {
count: preState.count + 1
}
} else {
return {
count: preState.count - 1
}
}
}, () => { })
}
render() {
return (
);
}
}
export default Counter;
Count.jsx
import React from 'react'
import Proptypes from 'prop-types'
class Count extends React.Component {
render(){
return {this.props.count}
}
}
Count.propTypes ={
count:Proptypes.number.isRequired
}
export default Count;
Button.jsx
import React, { Component } from 'react';
class Button extends Component {
handleClick(){
console.log('handle click')
this.props.change()
}
render() {
return (
);
}
}
export default Button;
import React, { Component } from 'react';
import axios from 'axios'
class Product extends Component {
constructor() {
super()
this.state = {
list: [],
total:0
}
}
sum(){
let total = this.state.list.reduce((total,item)=>{
return total += item.sc
},0)
this.setState({
total
})
}
render() {
return (
{
this.state.list.map((item)=>{
return - {item.nm}
})
}
总分:{this.state.total}
);
}
componentDidMount() {
axios.get('http://localhost:3000/datalist').then((result) => {
console.log(result);
this.setState({
list:result.data
})
this.sum();
})
}
}
export default Product;
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
直接在render里写行内的箭头函数(不推荐)
在组件内使用箭头函数定义一个方法(推荐)
handleClick =()=>{
console.log(this.state.name)
}
onClick={this.handleClick.bind(this)}
(不推荐) handleClick(event, val) {
console.log(event, val)
}
//在render里直接使用
constructor() {
super()
// 推荐
this.myhandleClick = this.handleClick.bind(this);
}
//render里面调用
和普通浏览器一样,事件handler会被自动传入一个 event
对象,这个对象和普通的浏览器 event
对象所包含的方法和属性都基本一致。不同的是 React中的 event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
render
里调用方法的地方外面包一层箭头函数render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递event
传递props
传递到子组件中,然后在子组件件通过this.props.method
来调用
React组件的数据渲染是否被调用者传递的props
完全控制,控制则为受控组件,否则非受控组件。
import React, { Component,createRef } from 'react';
class Form extends Component {
constructor() {
super();
this.state = {
name: "",
desc: "",
city: [1,2]
}
this.address = React.createRef()
}
handleChange(event) {
console.log(event.target.value)
if(event.target.name==='city'){
this.state.city.push(event.target.value);
this.setState({})
return;
}
this.setState({
[event.target.name]: event.target.value.toUpperCase()
// [event.target.name]: event.target.value
})
}
handleSubmit = () => {
console.log(this.state,this.address.current.value)
}
render() {
return (
名称:
描述:
城市:
);
}
}
export default Form;
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)
http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
在组件初始化阶段会执行
##更新阶段
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)
getDerivedStateFromProps
是react16.3之后新增,在组件实例化后,和接受新的props
后被调用。他必须返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。
如果是由于父组件的props
更改,所带来的重新渲染,也会触发此方法。
调用steState()
不会触发getDerivedStateFromProps()
。
之前这里都是使用constructor
+componentWillRecieveProps
完成相同的功能的
#####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()。
#####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。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。
##PureComponent
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
class YourComponent extends PureComponent {
……
}
##ref
React提供的这个ref
属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()
返回的组件实例,ref
可以挂载到组件上也可以是dom元素上。
class
声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:在React 最新的版本中,要使用ref
, 需要使用React.createRef
方法先生成一个ref
。
import React, { Component,createRef } from 'react';
...
constructor() {
super();
this.address = createRef()
}
.....
...
//取值
this.address.current.value
Higher-Order Components就是一个函数,传给它一个组件,它返回一个新的组件。
const NewComponent = higherOrderComponent(YourComponent)
#组件通信
父组件与子组件通信
父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
父组件利用ref
对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法…
子组件与父组件通信
this.props
接收到父组件的方法后调用。跨组件通信
在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context
api来实现跨组件通信, React 16.3之后的context
api较之前的好用。
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组件
总是渲染第一个匹配到的组件
处理404与默认页
withRoute高阶组件的使用
管理一个项目路由的方法
code spliting
HashRouter和BrowserRouter的区别,前端路由和后端路由的区别。这个在Vue里应该有讲过了。
React Router甚至大部分的前端路由都是依赖于history.js
的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。
hash
来存储在不同状态下的history
信息,对应createHashHistory
,通过检测location.hash
的值的变化,使用location.replace
方法来实现url跳转。通过注册监听window
对象上的hashChange
事件来监听路由的变化,实现历史记录的回退。createBrowserHistory
, 使用包括pushState
, replaceState
方法来进行跳转。通过注册监听window
对象上的popstate
事件来监听路由的变化,实现历史记录的回退。createMemoryHistory
。直接在内存里push
和pop
状态。#状态管理
##Redux
官网
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
需要使用Redux的项目:
从组件层面考虑,什么样子的需要Redux:
Redux的设计思想:
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
Redux的使用的三大原则:
Document
Redux的流程:
1.store通过reducer创建了初始状态
2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
3.用户产生了操作,调用了actions 的方法
4.actions的方法被调用,创建了带有标示性信息的action
5.actions将action通过调用store.dispatch方法发送到了reducer中
6.reducer接收到action并根据标识信息判断之后返回了新的state
7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state
Reducer必须是一个纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce
, 它的第一个参数就是一个reducer
纯函数是函数式编程的概念,必须遵守以下一些约束。
不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state = defaultState, action) {
return Object.assign({
}, state, {
thingToChange });
// 或者
return {
...state, ...newState };
}
// State 是一个数组
function reducer(state = defaultState, action) {
return [...state, newItem];
}
最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。
我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。
划分reducer:
因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起
注意:
- 分离reducer的时候,每一个reducer维护的状态都应该不同
- 通过store.getState获取到的数据也是会按照reducers去划分的
- 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态
关于action/reducer/store的更多概念,请查看官网
Redux异步
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
常见的异步库:
基于Promise的异步库:
###容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
直接使用 Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
调用方式 | 手动 | 通常由 React Redux 生成 |
可以先结合context
来手动连接react和redux。(https://react-redux.js.org/)
react-redux提供两个核心的api:
Provider
根据单一store原则 ,一般只会出现在整个应用程序的最顶层。
connect
语法格式为
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
一般来说只会用到前面两个,它的作用是:
store.getState()
的状态转化为展示组件的props
actionCreators
转化为展示组件props
上的方法特别强调:
官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators
只要上层中有Provider
组件并且提供了store
, 那么,子孙级别的任何组件,要想使用store
里的状态,都可以通过connect
方法进行连接。如果只是想连接actionCreators
,可以第一个参数传递为null
https://es6.ruanyifeng.com/#docs/decorator
装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个提案将其引入了 ECMAScript。
装饰器是一种函数,写成@ + 函数名
。它可以放在类和类方法的定义前面。
React Hooks 是 React 16.7.0-alpha
版本推出的新特性, 有了React Hooks,在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期。通过下面几个例子来学习React Hooks。
// useState是react包提供的一个方法
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
return (
你点击了{count}次
);
};
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
// useState是react包提供的一个方法
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
// 类似于componentDidMount或者componentDidUpdate:
useEffect(() => {
// 更改网页的标题,还可以做其它的监听
document.title = `你点击了${count}次`;
});
return (
你点击了{count}次
);
};
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
useState
useEffect
useContext
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
](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的方法其实很简单——分片。把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
我们以前操作dom的方式是通过document.getElementById()的方式,这样的过程实际上是先去读取html的dom结构,将结构转换成变量,再进行操作
而reactjs定义了一套变量形式的dom模型,一切操作和换算直接在变量中,这样减少了操作真实dom,性能真实相当的高,和主流MVC框架有本质的区别,并不和dom打交道
react最核心的思想是将页面中任何一个区域或者元素都可以看做一个组件 component
那么什么是组件呢?
组件指的就是同时包含了html、css、js、image元素的聚合体
使用react开发的核心就是将页面拆分成若干个组件,并且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式
其实reactjs的核心内容就是数据绑定,所谓数据绑定指的是只要将一些服务端的数据和前端页面绑定好,开发者只关注实现业务就行了
在vue中,我们使用render函数来构建组件的dom结构性能较高,因为省去了查找和编译模板的过程,但是在render中利用createElement创建结构的时候代码可读性较低,较为复杂,此时可以利用jsx语法来在render中创建dom,解决这个问题,但是前提是需要使用工具来编译jsx
#create-react-app
全局安装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. 安装过程较慢,可以推荐学员使用yarn
Installing react, react-dom, and react-scripts...
如果不想全局安装,可以直接使用npx
$ npx create-react-app your-app 也可以实现相同的效果
这需要等待一段时间,这个过程实际上会安装三个东西
出现下面的界面,表示创建项目成功:
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 install命令
npm cache clean --force
之后再执行npm install
命令//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
hello world
,
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
const app = hello world
ReactDOM.render(
app,
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
const app =(props)=> 欢迎进入{props.name}的世界
ReactDOM.render(
app({
name:'react'
}),
document.getElementById('root')
);
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
//组件名称必须大写
const App =(props)=> 欢迎进入{props.name}的世界
ReactDOM.render(
,
document.getElementById('root')
);
ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class
是目前官方推荐的使用方式
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
class App extends React.Component {
//必须要实现
render() {
return (
欢迎来到{this.props.name}的世界
)
}
}
ReactDOM.render(
,
document.getElementById('root')
);
在16以前的版本还支持这样创建组件, 但现在的项目基本上不用
yarn add create-react-class -S
//基础库,支持jsx
import React from 'react';
//帮助我们把组件渲染到页面上
import ReactDOM from 'react-dom';
import createClass from 'create-react-class';
var Hello = createClass({
render(){
return gp18
}
})
ReactDOM.render(
,
document.getElementById('root')
);
import React,{Fragment} from 'react'
import Hotshowing from './Hotshowing'
import Comingsoon from './Comingsoon'
export default class Movie extends React.Component {
render() {
return (
// Fragment
<>
>
)
}
}
//Comingsoon.jsx
import React, { Component } from 'react';
class Comingsoon extends Component {
render() {
return (
coming soon page
);
}
}
export default Comingsoon;
//Hotshowing.jsx
import React, { Component } from 'react';
class Hotshowing extends Component {
render() {
return (
hotshowing page
);
}
}
export default Hotshowing;
#JSX 原理
基础:https://react.docschina.org/docs/introducing-jsx.html
import React, { Component } from 'react';
class Login extends Component {
fullname(firstname, lastname) {
return firstname + lastname;
}
render() {
let islogin = sessionStorage.getItem('username')
if (islogin) {
let username = sessionStorage.getItem('username');
return (
{1 + 2 + 3}
欢迎, {username}
{
this.fullname('han','ye')
}
);
} else {
return 您还没有登录
}
}
}
export default Login;
要明白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样式
import React, { Component } from 'react';
import "./Movie.css"
import common from "./common.module.css"
class Movie extends Component {
constructor(){
super();
this.state ={
styleObj:{
fontSize:'50px'
}
}
}
render() {
return (
// 使用class
{/* 行内样式 */}
正在热映
即将上映
{/* module css */}
异常信息
);
}
}
export default Movie;
classnames: https://www.npmjs.com/package/classnames
npm install classnames --save
import React, { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './styles.css'
let cx = classNames.bind(styles);
class Profile extends Component {
render() {
let names = cx({
inProcess:true,
error:this.props.error
})
return (
gp18
);
}
}
export default Profile;
styled-components`是针对React写的一套css-in-js框架,简单来讲就是在js中写css
yarn add styled-components -S
import React from 'react'
import styled from 'styled-components'
const Container = styled.div`
width:500px;
height:500px;
background:${(props)=>props.color};
font-size:30px;
h1 {
font-size:50px;
}
`
export {
Container
}
import React, { Component } from 'react';
import {Container} from './Search_style'
class Search extends Component {
render() {
return (
gp18
hello world
);
}
}
export default Search;
#案例-TodoList
props
是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
###设置组件的默认props
import React, { Component } from 'react';
class Title extends Component {
//第一种定义方式
static defaultProps = {
name:"react"
}
render() {
return (
欢迎进入{this.props.name} 的世界
);
}
}
//第二种方式
// Title.defaultProps = {
// name:'react'
// }
export default Title;
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children
。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。
ReactDOM.render(
gp18
react
,
document.getElementById('root')
);
import React, { Component } from 'react';
class Content extends Component {
render() {
return (
{
this.props.children
}
);
}
}
export default Content;
import React, { Component } from 'react';
class Title extends Component {
render() {
return (
欢迎进入{this.props.children} 的世界
);
}
}
export default Title;
React其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的props
设定参数检查,需要安装和使用prop-types:
$ npm i prop-types -S
import React, { Component } from 'react';
import Proptypes from 'prop-types'
class Product extends Component {
render() {
return (
产品名称: {this.props.name}
);
}
}
Product.propTypes={
name:Proptypes.string,
//['北京','天津']
// city:Proptypes.arrayOf(Proptypes.string).isRequired,
// customProp:function(props,PropName){
// if(!/gp/.test(props[PropName])){
// return new Error('内容非法')
// }
// console.log(arguments)
// },
customArrayProp: Proptypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/北京/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
}).isRequired
}
export default Product;
##状态(state)
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
###定义state
constructor() {
super();
//state定义的第一种方式
//推荐
// this.state = {
// count: 1,
// title: "中国机长"
// }
}
//state定义的第二种方式
state ={
title:"中国机长",
count:1
}
###setState
import React, { Component } from 'react';
class Movie extends Component {
constructor() {
super();
//state定义的第一种方式
// this.state = {
// count: 1,
// title: "中国机长"
// }
}
//state定义的第二种方式
state ={
title:"中国机长",
count:1
}
componentDidMount() {
// setTimeout(() => {
//修改state的第一种方式
// this.setState({
// title: "战狼2"
// })
// }, 2000)
//修改state的第二种方式
// this.state.title ="战狼2"
// setTimeout(()=>{
// this.setState({})
// },2000)
//修改state的数据是异步的
// this.setState({
// count: this.state.count + 1
// })
// console.log(this.state.count)
//修改state的第三种方式
this.setState((preState, props) => {
console.log(props);
//数据的更新,会做merge
return {
count: preState.count + 1
}
}, () => {
console.log(this.state.count)
})
setTimeout(()=>{
this.setState({
count:3
})
console.log(this.state.title,this.props.city)
},3000)
}
render() {
console.log(1)
return (
{this.state.title}
);
}
}
export default Movie;
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过 this.setState
方法进行更新,setState
会导致组件的重新渲染。
props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props
,否则组件的 props
永远保持不变。
如果搞不清 state
和 props
的使用场景,记住一个简单的规则:尽量少地用 state
,多用 props
。
没有 state
的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
##状态提升
如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
条件渲染
列表渲染
import React, { Component } from 'react';
import Comp1 from './Com1';
import Comp2 from './Com2'
class People extends Component {
state = {
condition: false,
list: [{
id: 1,
name: 'hanye',
age: 20
}, {
id: 2,
name: "周光磊",
age: 18
}]
}
render() {
return (
<>
{
this.state.condition ? :
}
{
this.state.list.map((person) => {
return (- name:{person.name}-age:{person.age}
)
})
}
>
);
}
}
export default People;
Counter.jsx
import React, { Component } from 'react';
import Button from './Button'
import Count from './Count'
import styled from 'styled-components'
const CountWrapper = styled.div`
h1 {
display:block;
color:red;
}
button {
width:100px;
height:40px;
background:blue;
color:white;
font-size:20px;
}
`
class Counter extends Component {
constructor() {
super();
this.state = {
count: 0
}
}
handleChange(type) {
console.log('handle change')
this.setState((preState, props) => {
if (type === 'increment') {
return {
count: preState.count + 1
}
} else {
return {
count: preState.count - 1
}
}
}, () => { })
}
render() {
return (
);
}
}
export default Counter;
Count.jsx
import React from 'react'
import Proptypes from 'prop-types'
class Count extends React.Component {
render(){
return {this.props.count}
}
}
Count.propTypes ={
count:Proptypes.number.isRequired
}
export default Count;
Button.jsx
import React, { Component } from 'react';
class Button extends Component {
handleClick(){
console.log('handle click')
this.props.change()
}
render() {
return (
);
}
}
export default Button;
import React, { Component } from 'react';
import axios from 'axios'
class Product extends Component {
constructor() {
super()
this.state = {
list: [],
total:0
}
}
sum(){
let total = this.state.list.reduce((total,item)=>{
return total += item.sc
},0)
this.setState({
total
})
}
render() {
return (
{
this.state.list.map((item)=>{
return - {item.nm}
})
}
总分:{this.state.total}
);
}
componentDidMount() {
axios.get('http://localhost:3000/datalist').then((result) => {
console.log(result);
this.setState({
list:result.data
})
this.sum();
})
}
}
export default Product;
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
直接在render里写行内的箭头函数(不推荐)
在组件内使用箭头函数定义一个方法(推荐)
handleClick =()=>{
console.log(this.state.name)
}
onClick={this.handleClick.bind(this)}
(不推荐) handleClick(event, val) {
console.log(event, val)
}
//在render里直接使用
constructor() {
super()
// 推荐
this.myhandleClick = this.handleClick.bind(this);
}
//render里面调用
和普通浏览器一样,事件handler会被自动传入一个 event
对象,这个对象和普通的浏览器 event
对象所包含的方法和属性都基本一致。不同的是 React中的 event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
render
里调用方法的地方外面包一层箭头函数render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递event
传递props
传递到子组件中,然后在子组件件通过this.props.method
来调用
React组件的数据渲染是否被调用者传递的props
完全控制,控制则为受控组件,否则非受控组件。
import React, { Component,createRef } from 'react';
class Form extends Component {
constructor() {
super();
this.state = {
name: "",
desc: "",
city: [1,2]
}
this.address = React.createRef()
}
handleChange(event) {
console.log(event.target.value)
if(event.target.name==='city'){
this.state.city.push(event.target.value);
this.setState({})
return;
}
this.setState({
[event.target.name]: event.target.value.toUpperCase()
// [event.target.name]: event.target.value
})
}
handleSubmit = () => {
console.log(this.state,this.address.current.value)
}
render() {
return (
名称:
描述:
城市:
);
}
}
export default Form;
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)
http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
在组件初始化阶段会执行
1. constructor
2. static getDerivedStateFromProps()
3. componentWillMount() / UNSAFE_componentWillMount()
4. render()
5. componentDidMount()
##更新阶段
props
或state
的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
1. componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
2. static getDerivedStateFromProps()
3. shouldComponentUpdate()
4. componentWillUpdate() / UNSAFE_componentWillUpdate()
5. render()
6. getSnapshotBeforeUpdate()
7. componentDidUpdate()
##卸载阶段
##错误处理
##各生命周期详解
#####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)
getDerivedStateFromProps
是react16.3之后新增,在组件实例化后,和接受新的props
后被调用。他必须返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。
如果是由于父组件的props
更改,所带来的重新渲染,也会触发此方法。
调用steState()
不会触发getDerivedStateFromProps()
。
之前这里都是使用constructor
+componentWillRecieveProps
完成相同的功能的
#####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()。
#####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。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。
##PureComponent
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
class YourComponent extends PureComponent {
……
}
##ref
React提供的这个ref
属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()
返回的组件实例,ref
可以挂载到组件上也可以是dom元素上。
class
声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:在React 最新的版本中,要使用ref
, 需要使用React.createRef
方法先生成一个ref
。
import React, { Component,createRef } from 'react';
...
constructor() {
super();
this.address = createRef()
}
.....
...
//取值
this.address.current.value
Higher-Order Components就是一个函数,传给它一个组件,它返回一个新的组件。
const NewComponent = higherOrderComponent(YourComponent)
#组件通信
父组件与子组件通信
父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
父组件利用ref
对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法…
子组件与父组件通信
this.props
接收到父组件的方法后调用。跨组件通信
在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context
api来实现跨组件通信, React 16.3之后的context
api较之前的好用。
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组件
总是渲染第一个匹配到的组件
处理404与默认页
withRoute高阶组件的使用
管理一个项目路由的方法
code spliting
HashRouter和BrowserRouter的区别,前端路由和后端路由的区别。这个在Vue里应该有讲过了。
React Router甚至大部分的前端路由都是依赖于history.js
的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。
hash
来存储在不同状态下的history
信息,对应createHashHistory
,通过检测location.hash
的值的变化,使用location.replace
方法来实现url跳转。通过注册监听window
对象上的hashChange
事件来监听路由的变化,实现历史记录的回退。createBrowserHistory
, 使用包括pushState
, replaceState
方法来进行跳转。通过注册监听window
对象上的popstate
事件来监听路由的变化,实现历史记录的回退。createMemoryHistory
。直接在内存里push
和pop
状态。#状态管理
##Redux
官网
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
需要使用Redux的项目:
从组件层面考虑,什么样子的需要Redux:
Redux的设计思想:
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
Redux的使用的三大原则:
Document
Redux的流程:
1.store通过reducer创建了初始状态
2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
3.用户产生了操作,调用了actions 的方法
4.actions的方法被调用,创建了带有标示性信息的action
5.actions将action通过调用store.dispatch方法发送到了reducer中
6.reducer接收到action并根据标识信息判断之后返回了新的state
7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state
Reducer必须是一个纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce
, 它的第一个参数就是一个reducer
纯函数是函数式编程的概念,必须遵守以下一些约束。
不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state = defaultState, action) {
return Object.assign({
}, state, {
thingToChange });
// 或者
return {
...state, ...newState };
}
// State 是一个数组
function reducer(state = defaultState, action) {
return [...state, newItem];
}
最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。
我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。
划分reducer:
因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起
注意:
- 分离reducer的时候,每一个reducer维护的状态都应该不同
- 通过store.getState获取到的数据也是会按照reducers去划分的
- 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态
关于action/reducer/store的更多概念,请查看官网
Redux异步
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
常见的异步库:
基于Promise的异步库:
###容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
直接使用 Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
调用方式 | 手动 | 通常由 React Redux 生成 |
可以先结合context
来手动连接react和redux。(https://react-redux.js.org/)
react-redux提供两个核心的api:
Provider
根据单一store原则 ,一般只会出现在整个应用程序的最顶层。
connect
语法格式为
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
一般来说只会用到前面两个,它的作用是:
store.getState()
的状态转化为展示组件的props
actionCreators
转化为展示组件props
上的方法特别强调:
官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators
只要上层中有Provider
组件并且提供了store
, 那么,子孙级别的任何组件,要想使用store
里的状态,都可以通过connect
方法进行连接。如果只是想连接actionCreators
,可以第一个参数传递为null
https://es6.ruanyifeng.com/#docs/decorator
装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个提案将其引入了 ECMAScript。
装饰器是一种函数,写成@ + 函数名
。它可以放在类和类方法的定义前面。
React Hooks 是 React 16.7.0-alpha
版本推出的新特性, 有了React Hooks,在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期。通过下面几个例子来学习React Hooks。
// useState是react包提供的一个方法
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
return (
你点击了{count}次
);
};
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
// useState是react包提供的一个方法
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
// 类似于componentDidMount或者componentDidUpdate:
useEffect(() => {
// 更改网页的标题,还可以做其它的监听
document.title = `你点击了${count}次`;
});
return (
你点击了{count}次
);
};
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
useState
useEffect
useContext
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
](