React
文章目录
react项目移植出现的问题
当把我的react项目移植到另一台电脑时,因为要npm install 安装依赖库,但是对于采用npm install会带来一些库的版本问题,当时找了很多方法才解决,解决方法就是很简单:是采用yarn包管理工具,yarn会自己解决一些库的版本问题,使得在新环境中不会因版本问题报错
1. react是什么
React是一个用于构建用户界面的js库,
React主要作用是来写html页面,或构建web应用
从MVC角度,react仅仅是视图层V,也就是只负责视图的渲染,而并非提供了完整的M和C。
2. React的特点:
\1. 声明式,你只需要描述UI什么样子,跟写html页面一样
这里是用到了jsx语法创建react元素:
Const jsx = <div>react帅哦</div>;
\2. 基于组件开发,组件是react中最重要的内容,组件就是页面中的一部分内容
\3. 学习一次,随便使用,可以开发网页,手机app、vr技术
react 高性能的体现:虚拟DOM
我们以前是操作真实的dom,通过document.querySelector()的方式。这样的过程实际上是先读取html的dom结构,将结构转换为变量,再进行操作。而reactjs定义了一套变量形式的虚拟dom模型(jsx+state),这是一种虚拟dom,一切操作和换算直接在变量中(也可以说一切操作都是在虚拟dom中进行的),state等数据修改完之后再转化为真实DOM渲染到浏览器页面中,这样就减少了操作真实DOM,性能更高了,和主流的MVC有本质的区别,并不和真实DOM树打交道。
其实虚拟DOM的真实价值从来都不是为了以提升性能为第一位,而是采用虚拟DOM可以使得react元素(虚拟DOM)脱离浏览器的束缚,让其可以跨平台地运行在其他Android端、vr环境中。我们开发React都是在面向虚拟DOM进行开发
3.React cli创建react项目:
npm init react-app my-app
4. React使用:
导入react、react-dom包
import React from 'react';
import ReactDOM from 'react-dom';
// 利用createElement()函数创建react元素,较为复杂
// const title = React.createElement('h1', null, 'react是你hi');
// 使用jsx的方式创建react元素
const title = <h1>react 你好帅哦!</h1>;
// 利用reactDOM进行渲染
ReactDOM.render(title, document.querySelector('#wh'));
5. JSX语法:
JSX并不是标准的ECMAScript语法,他是ES的语法扩展。
它需要使用babel编译处理之后,才能再浏览器环境中使用,而react-cli中已经默认配置了,所以在脚手架中就可以使用这种语法。
注意点:
react元素的属性名驼峰命名法
使用小括号包括jsx
const title = (<h1 className="w">react 你好帅哦!<span /> </h1>);
jsx总结
1.jsx是react的核心内容
2.jsx表示在js代码中写HTML节后,是React生命是的体现
3.使用jsx配合嵌入的js表达式、条件渲染、列表渲染可以描述任意UI结构
4.推荐使用className的方式给jsx添加样式
5.react完全是利用js语言自身的能力编写UI,而不是造轮子增强HTML功能。(这也是react和vue的区别,vue可以利用v-for、v-model指令以造轮子来增强html,而react是纯手写js代码实现渲染)
react特色:只要能用到js的地方就绝对不会增加一个新的语法
6. react组件
使用函数创建组件
// 第一种创建方式,同归js的函数创建组件
// 组件的首字母必须大写
function Hello() {
return (
<div>hello,我是你 !</div>
);
}
// 第二种创建组件方式类组件,使用ES6的class类创建的组件
class Hello2 extends React.Component {
render() {
return (
<div>hello2,react还是你 !</div>
)
}
}
绑定事件
//在类组件中添加事件,需要用到this才能触发函数
class Hello2 extends React.Component {
handlePrevent(e) {
// e为事件对象,react中的事件对象叫做合成事件(对象)
e.preventDefault();
console.log("react 我被阻止啦");
}
render() {
return (
<div>
<div>hello2,react还是你 !</div>
<a href="http://www.baidu.com" onClick={this.handlePrevent}>我是React</a>
</div>
)
}
}
//在函数组件中,不需要用this
function Hello() {
function handleClick() {
console.log(1);
}
return (
// 事件绑定
<div onClick={handleClick}>hello,我是你 !</div>
);
}
7.有状态组件和无状态组件
- 函数组件称为无状态组件,类组件称为有状态组件。
- 函数组件没有自己的数据,也叫做没有自己的状态,它只负责静态页面的渲染
- 而类组件有自己的状态,负责更新UI页面,负则把数据渲染到页面中,让页面动起来
state的基本使用
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用,state的值是一个对象,表示一个组件中可以含有多个数据,这些数据构成一个对象
class Count extends React.Component {
/* constructor() {
super();
this.state = {
count1: 1
}
} */
// 简化语法
state = {
count1: 0
}
render() {
return (
<div>
有状态组件{this.state.count1}
</div>
)
}
}
setState()修改状态(数据)
- 状态是可变的;
- 语法:this.setState({要修改的数据})
- 注意:不要直接修改state中的值,这是错误的,想要修改数据就必须在setState()中进行修改
class Count extends React.Component {
/* constructor() {
super();
this.state = {
count1: 1
}
} */
// 简化语法
state = {
count1: 0
}
// 这里只能使用箭头函数(this指向这个类),如果不用箭头函数,那么里面的this指向此handleAdd函数调用者,或者使用call()修改this指向
handleAdd() {
//调用setState()
this.setState({ count1: this.state.count1 + 1 });
}
render() {
return (
<div>
有状态组件:{this.state.count1}
<button onClick={this.handleAdd}>+</button>
</div>
)
}
}
8. 表单处理
受控组件:
步骤:
- 在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
- React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
- 受控组件:其值收到React控制的表单元素
非受控组件
借助于ref,使用原生DOM方式来获取表单元素值。
ref:ref就是用来获取DOM或组件的一个属性
使用步骤:
- 调用react.createRef()方法创建一个ref对象
- 把创建好的ref对象添加到文本框中国
- 通过ref对象的获取到文本框的值
class Whh extends React.Component {
constructor() {
super();
// 创建ref
this.myRef = React.createRef();
}
getTxt = () => {
console.log(this.myRef.current.value);
}
render() {
return (
//以后this.myRef就代表表这个DOM框了
<div>
<input type="text" ref={this.myRef}></input>
<button onClick={this.getTxt}>点我查看文字</button>
</div>
)
}
}
9. 组件间的数据传输props
组件是封闭的,要接收外部组件的数据要通过props来实现
prop的作用:
接收传递给组件的数据
-
第一步:传递数据:给组件标签添加属性
// 渲染APP3组件(用于学习prop相关的知识点),传递数据:name、age ReactDOM.render(<App3 name="react" age="23" />, document.querySelector("#prop1"))
-
第二步:接收数据:
class App3 extends React.Component {
// props是个对象
// 在类组件中使用this.props来获取传递过来的props数组,在函数组件时,在函数的参数中传递props参数
// name = this.props.name;
props1 = this.props.name;
render() {
console.log(this.props); //props是个数组
return (<div>props:{this.props1}</div>)
}
}
props的特点
- 可以给组件传递任意类型的数据
- props是个只读的对象,只能读取属性的值,是不能修改的!
- 注意:在使用类组件是,如果写了构造函数constructor(),应该将props传递给super(),否则,无法在构造函数中获取到props
constructor(props) {
super(props);
console.log(props);
}
组件之间通信的三种方式
父组件----》子组件
子组件----》父组件
兄弟组件之间通信
父向子组件传递数据:
- 第一步:父组件提供要传递的state数据
- 第二步:子组件接收数据
class Child extends React.Component {
render() {
return (
// 父向子传递第二步:从父组键接收到的props数据
<div>{this.props.name}</div>
)
}
}
class Parent extends React.Component {
state = {
name: "react"
}
render() {
return (
<div>
// 父向子传递第一步:向子组件传递来自父组件的state数据
传递数据给子组件<Child name={this.state.name} />
</div>
)
}
}
子组件向父组件传递数据:
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
- 第一步:父组件提供一个回调函数(用于接收数据)
- 第二步,通过props传递回调函数
- 第三步:子向父通过prop接收到的函数来向父组件传递参数
class Child extends React.Component {
render() {
console.log(this.props);
// 子向父第三步:子向父通过prop接收到的函数来向父组件传递参数
this.props.getmsg(" ");
return (
// 父向子传递第二步:从父组键接收到的props数据
<div>{this.props.name}</div>
)
}
}
class Parent extends React.Component {
// 子向父传递数据第一步,定义回调函数:
getChildMsg = (msg) => {
console.log("接收到的数据", msg);
}
state = {
name: "react"
}
render() {
return (
/* 父向子传递第一步:向子组件传递来自父组件的state数据 */
// 子向父传递数据第二步,通过props传递回调函数
<div >
传递数据给子组件<Child name={this.state.name} getmsg={this.getChildMsg} />
</div>
)
}
}
兄弟组件之间传递数据:
将共享状态提升到最近的公共组件中,由公共组件管理这个状态
思想:变量提升
公共组件负责:1.提供共享状态 2.提供操作共享状态的方法
要通讯的子组件只需要通过props接收状态和操作状态的方法
Context实现跨组件传递数据
使用Context实现跨组件传递数据(比如主题,语言等)
使用步骤:
-
调用React.createContext()创建Provider(用于提供数据)和Consumer(用于消费数据)两个组件。
const { Provider, Consumer } = React.createContext()
-
// 第二步:用Provider包裹,即作为父节点
// 第二步:用Provider包裹,即作为父节点 <Provider> <div> <Nodes /> </div> </Provider>
-
// 第三步:设置value属性,表示要传递的数据,提供数据也就是通过value传递
<Provider value="react ">
<div>
<Nodes />
</div>
</Provider>
- 通过Consumer组件接收数据
<div>
<Consumer>
{data => <div>data</div>}
</Consumer>
</div>
props高级
props.children属性
children属性:表示组件标签的子节点。当组件标签有子节点是,props就会有该属性,值可以是任意值(文本、React元素、组件、函数)
class Hello3 extends React.Component {
render() {
// props的children属性
console.log(this.props.children);
return (
<div>
组件的子节点{this.props.children}
</div>
)
}
}
props校验
props校验允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因为pros导致的错误,并给出明确的错误提示,增加组建的健壮性
import PropTypes from "prop-type"
Hello3.propsTypes = {
// 校验的colors元素是否出现不符合
colors: PropTypes.array
}
常见的约束规则:
array、bool、func、number、object、string、element(react元素)
必选项:isRequired
props的默认值
给一些属性设置默认值:
App.defaultProps = {
pageSize:10
} //此时就可以不用传入pageSize属性,因为已经设置了默认值
10. 组件的生命周期
组件的声明周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。
生命周期的每个阶段总是伴随着一些方法被调用,这些方法就是生命周期的钩子函数。
只有类组件才有生命周期,函数组件没有生命周期。
声明周期的三个阶段
创建时、更新时、卸载时。(vue中是创建前后、更新前后、销毁前后)
创建时期(挂载阶段)
当组件创建时(页面加载时)会有三个钩子函数被调用:
constructor() => render() => componentDidMount()
constructor()钩子函数作用:初始化state、为事件处理程序处理程序绑定this
render()钩子函数作用:,每次组件渲染时就会调用,用于渲染UI
componentDidMount()钩子函数作用:组件挂载时(完成DOM渲染后),也就是render之后立刻会执行。常用于用于发送网络请求(这和vue中大有不同,vue中的网络请求一般是发放在creat()页面被创建里面)、DOM操作
更新时期
组件发生更新时触发。
会触发render()、再触发componentDidUpdate();
componentDidUpdate()钩子函数可以用来发送ajax请求、DOM操作,但是如果要setState()必须要放在一个if条件中
卸载时期
组件在页面中消失时触发。
componentWillUnmout()钩子函数用于页面消失时触发,经常用于在页面消失时清除一些定时器
11. render-props和高阶组件
react组件复用:react组件复用的是state和操作state的方法(即组件状态逻辑)
主要有两种方式实现以上需求:render props模式 和高级组件(HOC)
注意:这两种方式不是新的API,而是利用react自身特点的编码技巧,演化而成的固定模式(一种写法)
render-props模式
使用步骤:
1.创建Mouse组件,在组件中提供服用的状态逻辑代码(1,状态;2,操作状态的方法)
2.将要复用的状态作为props.render(state)方法的参数暴露到组件外部
3.使用props.render()的返回值作为作为要渲染的内容
import React from "react"
// render props复用模式
// 大致使用步骤:
// 第一步:创建Mouse组件
class Mouse extends React.Component {
state = {
// 鼠标位置
x: 0,
y: 0
}
// 鼠标移动事件的事件处理程序
handleMove = e => {
console.log(e.clientX);
this.setState({
// e为鼠标对象,e.clientX为x坐标
x: e.clientX,
y: e.clientY
})
}
// 监听鼠标移动事件
componentDidMount = () => {
window.addEventListener("mousemove", this.handleMove)
}
render() {
return this.props.render(this.state)
}
}
class App6 extends React.Component {
render() {
return (
<div>
<h1>render props模式</h1>
//
<Mouse render={(mouse) => {
return (
<p>
鼠标位置:{mouse.x} {mouse.y}
</p>
)
}}></Mouse>
</div>
)
}
}
export { Mouse, App6 }
高阶组件
高阶组件用来实现状态逻辑复用,采用包装(装饰)模式来进行包装组件,增强组件功能。
高阶组件(HOC)相当于是一个函数,接收要包装的组件,返回增强的组件。
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过props将复用的状态传递给被包装组件
import React from "react"
/* 高阶组件的使用 */
// 创建高阶组件,
// 创建一个函数,名字以with开头(约定),指定函数参数,参数为一个组件名字(首字母必须大写撒)
function withMouse(WrappedComponent) {
// 在函数内部必须创建一个类组件。提供复用的状态逻辑代码,并返回
class Mouse1 extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0
}
handleMove = e => {
console.log(e.clientX);
this.setState({
x: e.clientX,
y: e.clientY
})
}
//控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener("mousemove", this.handleMove)
}
componentWillUnmount() {
window.removeEventListener("mousemove", this.handleMove);
}
render() {
// 在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
// 设置高级组件的展示名字
Mouse1.displayName = `WithMouse${getDisplayName(WrappedComponent)}`;
return Mouse1;
}
function getDisplayName(wrappedComponent) {
return wrappedComponent.displayName || wrappedComponent.name || 'component';
}
// 用来测试高阶组件
const Position = (props) => {
console.log(props);
return (
<p>鼠标当前位置:{props.x} {props.y}</p>
)
}
// 调用高级组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
const NewPosition = withMouse(Position);
export { NewPosition }
组件极简模型:(state,props)=> UI
意思是:组件内部提供状态,接收外部传来的props,最终组件内部消化处理之后得到你想要展示的UI结构
12. react原理揭秘
setState()的说明
setstate()是异步的。
setState()推荐语法:
//跟之前的不同,传入的不是对象而是一个回调函数,其中的参数state为最新的state数据
this.setState((state,props)=>{
return {
count: state.count+1
}
})
//setState()其实可以传入两个回调函数,即this.setState(回调1,回调2),回调2函数会在回调1函数执行完毕之后并且页面渲染完成后立即执行
this.setState(
(state,props)=>{
return {
count: state.count+1
}
},
()=>{
this.log("22")
}
)
JSX语法的转化过程
JSX仅仅是createElement()方法的语法糖(语法糖就是简化语法的意思撒)
jsx语法会被@babel/preset-react插件编译为createElement()方法
react元素:是一个对象,用来描述你希望在屏幕上看到的内容
jsx语法 =》 createElement() =》 React元素
组件更新机制
setState()作用:1,修改当前state 2. 更新组件(UI)
过程:父组件重新渲染时,也会重新渲染子组件(但不会重新渲染它的父组件,只会重新更新它的子组件及子组件的子组件)。但只会渲染当前子组件树(当前组件及其所有子组件)
组件性能优化
- 减轻state
state中只存储跟组件渲染相关的数据。不做渲染的数据不要放入state中,比如一些定时器id等,应该放在this中,比如:
this.timeId = setInterval(()=>{},1000)
- 避免不要的重新渲染
根据组件更新机制,父组件更新则其子组件也会被更新,而有的时候子组件没有必要总是被重新渲染,为了解决子组件不必要的重新渲染,则需通过钩子函数shouldComponentUpdate(nextProps,nextState){},根据条件return false,就会停止接下来的render()钩子函数进行渲染。
shouldComponentUpdate(){
//根据条件,决定是否重新渲染组件
return false;
}
// 若返回false,则就不会再调用render()钩子函数来渲染
render(){}
- 纯组件
使用纯组件可以解决:得当父组件的state发生变化时,子组件会重新渲染的问题
当我们把子组件改成 PureComponent,即,当检测到 props 没有变化的时候,并不重新渲染。
import React, { Component,PureComponent } from 'react';
class Son extends PureComponent {
constructor(props) {
super(props);
this.state = { }
}
render() {
console.log('son render')
return (<div>
{this.props.value}
</div> );
}
}
export default Son;
13. 虚拟DOM和Diff算法
当组件中只有一个DOM元素需要更新时,需要把整个组件的内容重新渲染到页面中吗,答案肯定不是的!因为这样做会使的性能React性能极差。
我们只需要对其组件中的部分改变的内容进行重新渲染。这就需要用到虚拟DOM配合diff算法来实现组件的部分更新。
虚拟DOM:本质就是一个js对象,用来描述你希望在屏幕上看到的内容(UI)。
Diff算法:
执行过程:
-
初次渲染时,React会根据初始state数据,创建一个虚拟DOM对象(树)
-
根据虚拟DOM生成真实的DOM,然后再渲染到页面中
-
当state数据发生改变后(setState()之后),会重新根据新的数据,创建新的虚拟DOM对象(树)
-
然后与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同,核心是采用patch()函数),得到需要更新的内容
-
最终React只将变化的内容更新(通过patch()函数将新节点改变的地方刷新纠结点)到真实DOM中,重新渲染页面
import React from "react"
class App7 extends React.Component {
state = {
count: 0
}
handleClick = () => {
this.setState((state, props) => {
return {
count: state.count + 1
}
})
}
// render方法调用并不意味着浏览器所有元素的重新渲染
// render方法调用仅仅说明要进行diff,对比前后的虚拟DOM,把变化的内容更新到真实DOM树上,然后根据真实dom进行渲染
render() {
// el只是一个虚拟dom对象,不是真实dom
let el = (
<div>
{this.state.count}
<button onClick={this.handleClick}>
+1
</button>
</div>
);
return el;
}
}
//修改app7的展示名字
App7.displayName = "App77";
export { App7 }
14. React路由
现在的前端应用大多数是SPA(即单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中,是URL路径与组件的对应关系,使用React路由简单来说就是配置路径和组件(配对)
路由基本使用
使用步骤:
- cnpm i react-router-dom
- 导入路由的三个核心组件:Router/Route/Link
// 导入路由三大组件
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
- 必须使用Router组件包裹整个应用(重要)
const App8 = function () {
return (
// 使用router组件进行包裹
< Router >
<div>
<h1>react路由的使用</h1>
</div>
</ Router >
);
}
- 使用Link组件作为导航菜单(路由入口)
< Router >
<div>
<h1>react路由的使用</h1>
// Link组件会被渲染为a标签,to会被渲染为href属性
<Link to="/first">点我跳转到页面1</Link>
</div>
</ Router >
- 使用Router组件配置路由规则和要展示的组件(路由出口)
{/*第四步: 利用Route组件,path属性指定路由出口 */}
<Route path="/first" component={First} />
整个流程:
import React from "react"
// 第一步:导入路由三大组件
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
/**
* react路由使用
*/
const First = () => <p>页面1内容</p>
const App8 = function () {
return (
// 第二步:使用Router组件进行包裹
< Router >
<div>
<h1>react路由的使用</h1>
{/*第三步: Link组件为路由入口Link组件会被渲染为a标签,to会被渲染为href属性 */}
<Link to="/first">点我跳转到页面1</Link>
{/*第四步: 利用Route组件,path属性指定路由出口 */}
<Route path="/first" component={First} />
</div>
</ Router >
);
}
默认路由:当页面一加载时,就会匹配的路由;
<Route path="/",component={Home}></Route>
路由的执行过程
- 点击Link组件(a标签),就会修改浏览器地址栏中的url
- React路由监听到地址栏url的变化
- React路由内部便利所有的Route组件,使用路由规则(path)于pathName(url里的路径)进行匹配
- 当路由规则(path)能够匹配到地址栏中的pathname时,就会展示该Route组件的内容
编程式导航
场景:点击登录按钮,登录成功后,通过js代码跳转到后台首页。
这就需要有编程式导航:通过js代码实现页面跳转
通过history.push(“路径”)进行跳转,history是react路由提供的,用于获取浏览器历史记录的相关信息
用history对象的go(-1)函数进行跳转到上一个页面 props.history.go(-1);
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
class Login extends React.Component {
handleClick = () => {
// 编程式路由跳转页面,通过使用history对象的push方法
this.props.history.push("/home")
}
render() {
return (
<div>
<p>登录页面</p>
<button onClick={this.handleClick}>react 点我登录撒</button>
</div>
);
}
}
const Home = (props) => {
let handleClick = () => {
// 用history对象的go(-1)函数进行跳转到上一个页面
props.history.go(-1);
}
return (
<div>
<h2>我是后台哟</h2>
<button onClick={handleClick}>点我返回登录页面</button>
</div>
)
}
class App9 extends react.Component {
render() {
return (
<Router>
<div>
<h1>这里是编程式路由撒</h1>
<Link to="/login">点我跳往登录页</Link>
{/* 点击跳往登录页面 */}
<Route path="/login" component={Login}></Route>
<Route path="/home" component={Home}></Route>
</div>
</Router>
)
}
}
export { App9 }
匹配模式
1.模糊匹配模式
当link组件的to属性值为“/login”时,默认路由“/”也会被匹配,这就是模糊匹配
在默认情况下,React路由为模糊匹配模式
模糊匹配规则:只要pathname(url中的路径名)以path开头就会不仅匹配pathname路由,还会匹配path路由。
2.精准匹配模式
问题:默认路由任何情况下都会展示,如何避免这种问题?
由默认模糊匹配改为精准匹配方法的方法:给Route组件添加exact属性,让此组件展示页改为精准匹配模式。
推荐:一般都会给默认路由添加exact属性
{/* 给组件加入exact属性时就会使其由模糊匹配变为精准匹配模式 */}
<Route exact path="/" component={() => { return (<div>我是主页</div>) }}></Route>
好客租房项目阶段
项目技术栈介绍:
React核心库:react、react-dom、react-router-dom
脚手架:create-react-app(react脚手架名字为create-react-app,在vue种教授架名字为vue-cli)
npx create-react-app “项目名字”
或者 npm init react- app “项目名字”
数据请求:axios
UI组件库:antd-mobile(antDesign-mobile)阿里提供的移动端的组件库
其他组件库:react-virtualized(列表组件库)、formik+yup(表单组件库)、react-spring(动画组件库)
百度地图API