当把我的react项目移植到另一台电脑时,因为要npm install 安装依赖库,但是对于采用npm install会带来一些库的版本问题,当时找了很多方法才解决,解决方法就是很简单:是采用yarn包管理工具,yarn会自己解决一些库的版本问题,使得在新环境中不会因版本问题报错
React是一个用于构建用户界面的js库,
React主要作用是来写html页面,或构建web应用
从MVC角度,react仅仅是视图层V,也就是只负责视图的渲染,而并非提供了完整的M和C。
\1. 声明式,你只需要描述UI什么样子,跟写html页面一样
这里是用到了jsx语法创建react元素:
Const jsx = <div>react帅哦</div>;
\2. 基于组件开发,组件是react中最重要的内容,组件就是页面中的一部分内容
\3. 学习一次,随便使用,可以开发网页,手机app、vr技术
我们以前是操作真实的dom,通过document.querySelector()的方式。这样的过程实际上是先读取html的dom结构,将结构转换为变量,再进行操作。而reactjs定义了一套变量形式的虚拟dom模型(jsx+state),这是一种虚拟dom,一切操作和换算直接在变量中(也可以说一切操作都是在虚拟dom中进行的),state等数据修改完之后再转化为真实DOM渲染到浏览器页面中,这样就减少了操作真实DOM,性能更高了,和主流的MVC有本质的区别,并不和真实DOM树打交道。
其实虚拟DOM的真实价值从来都不是为了以提升性能为第一位,而是采用虚拟DOM可以使得react元素(虚拟DOM)脱离浏览器的束缚,让其可以跨平台地运行在其他Android端、vr环境中。我们开发React都是在面向虚拟DOM进行开发
npm init react-app my-app
导入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 = react 你好帅哦!
;
// 利用reactDOM进行渲染
ReactDOM.render(title, document.querySelector('#wh'));
JSX并不是标准的ECMAScript语法,他是ES的语法扩展。
它需要使用babel编译处理之后,才能再浏览器环境中使用,而react-cli中已经默认配置了,所以在脚手架中就可以使用这种语法。
注意点:
react元素的属性名驼峰命名法
使用小括号包括jsx
const title = (react 你好帅哦!
);
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的地方就绝对不会增加一个新的语法
// 第一种创建方式,同归js的函数创建组件
// 组件的首字母必须大写
function Hello() {
return (
hello,我是你 !
);
}
// 第二种创建组件方式类组件,使用ES6的class类创建的组件
class Hello2 extends React.Component {
render() {
return (
hello2,react还是你 !
)
}
}
//在类组件中添加事件,需要用到this才能触发函数
class Hello2 extends React.Component {
handlePrevent(e) {
// e为事件对象,react中的事件对象叫做合成事件(对象)
e.preventDefault();
console.log("react 我被阻止啦");
}
render() {
return (
hello2,react还是你 !
我是React
)
}
}
//在函数组件中,不需要用this
function Hello() {
function handleClick() {
console.log(1);
}
return (
// 事件绑定
hello,我是你 !
);
}
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用,state的值是一个对象,表示一个组件中可以含有多个数据,这些数据构成一个对象
class Count extends React.Component {
/* constructor() {
super();
this.state = {
count1: 1
}
} */
// 简化语法
state = {
count1: 0
}
render() {
return (
有状态组件{this.state.count1}
)
}
}
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 (
有状态组件:{this.state.count1}
)
}
}
步骤:
借助于ref,使用原生DOM方式来获取表单元素值。
ref:ref就是用来获取DOM或组件的一个属性
使用步骤:
class Whh extends React.Component {
constructor() {
super();
// 创建ref
this.myRef = React.createRef();
}
getTxt = () => {
console.log(this.myRef.current.value);
}
render() {
return (
//以后this.myRef就代表表这个DOM框了
)
}
}
组件是封闭的,要接收外部组件的数据要通过props来实现
接收传递给组件的数据
第一步:传递数据:给组件标签添加属性
// 渲染APP3组件(用于学习prop相关的知识点),传递数据:name、age
ReactDOM.render( , 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 (props:{this.props1})
}
}
constructor(props) {
super(props);
console.log(props);
}
父组件----》子组件
子组件----》父组件
兄弟组件之间通信
class Child extends React.Component {
render() {
return (
// 父向子传递第二步:从父组键接收到的props数据
{this.props.name}
)
}
}
class Parent extends React.Component {
state = {
name: "react"
}
render() {
return (
// 父向子传递第一步:向子组件传递来自父组件的state数据
传递数据给子组件
)
}
}
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
class Child extends React.Component {
render() {
console.log(this.props);
// 子向父第三步:子向父通过prop接收到的函数来向父组件传递参数
this.props.getmsg(" ");
return (
// 父向子传递第二步:从父组键接收到的props数据
{this.props.name}
)
}
}
class Parent extends React.Component {
// 子向父传递数据第一步,定义回调函数:
getChildMsg = (msg) => {
console.log("接收到的数据", msg);
}
state = {
name: "react"
}
render() {
return (
/* 父向子传递第一步:向子组件传递来自父组件的state数据 */
// 子向父传递数据第二步,通过props传递回调函数
传递数据给子组件
)
}
}
将共享状态提升到最近的公共组件中,由公共组件管理这个状态
思想:变量提升
公共组件负责:1.提供共享状态 2.提供操作共享状态的方法
要通讯的子组件只需要通过props接收状态和操作状态的方法
使用Context实现跨组件传递数据(比如主题,语言等)
使用步骤:
调用React.createContext()创建Provider(用于提供数据)和Consumer(用于消费数据)两个组件。
const { Provider, Consumer } = React.createContext()
// 第二步:用Provider包裹,即作为父节点
// 第二步:用Provider包裹,即作为父节点
// 第三步:设置value属性,表示要传递的数据,提供数据也就是通过value传递
<Provider value="react ">
<div>
<Nodes />
</div>
</Provider>
<div>
<Consumer>
{data => <div>data</div>}
</Consumer>
</div>
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属性,因为已经设置了默认值
组件的声明周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。
生命周期的每个阶段总是伴随着一些方法被调用,这些方法就是生命周期的钩子函数。
只有类组件才有生命周期,函数组件没有生命周期。
创建时、更新时、卸载时。(vue中是创建前后、更新前后、销毁前后)
当组件创建时(页面加载时)会有三个钩子函数被调用:
constructor() => render() => componentDidMount()
constructor()钩子函数作用:初始化state、为事件处理程序处理程序绑定this
render()钩子函数作用:,每次组件渲染时就会调用,用于渲染UI
componentDidMount()钩子函数作用:组件挂载时(完成DOM渲染后),也就是render之后立刻会执行。常用于用于发送网络请求(这和vue中大有不同,vue中的网络请求一般是发放在creat()页面被创建里面)、DOM操作
组件发生更新时触发。
会触发render()、再触发componentDidUpdate();
componentDidUpdate()钩子函数可以用来发送ajax请求、DOM操作,但是如果要setState()必须要放在一个if条件中
组件在页面中消失时触发。
componentWillUnmout()钩子函数用于页面消失时触发,经常用于在页面消失时清除一些定时器
react组件复用:react组件复用的是state和操作state的方法(即组件状态逻辑)
主要有两种方式实现以上需求:render props模式 和高级组件(HOC)
注意:这两种方式不是新的API,而是利用react自身特点的编码技巧,演化而成的固定模式(一种写法)
使用步骤:
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结构
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仅仅是createElement()方法的语法糖(语法糖就是简化语法的意思撒)
jsx语法会被@babel/preset-react插件编译为createElement()方法
react元素:是一个对象,用来描述你希望在屏幕上看到的内容
jsx语法 =》 createElement() =》 React元素
setState()作用:1,修改当前state 2. 更新组件(UI)
过程:父组件重新渲染时,也会重新渲染子组件(但不会重新渲染它的父组件,只会重新更新它的子组件及子组件的子组件)。但只会渲染当前子组件树(当前组件及其所有子组件)
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;
当组件中只有一个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 }
现在的前端应用大多数是SPA(即单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中,是URL路径与组件的对应关系,使用React路由简单来说就是配置路径和组件(配对)
使用步骤:
// 导入路由三大组件
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
const App8 = function () {
return (
// 使用router组件进行包裹
< Router >
<div>
<h1>react路由的使用</h1>
</div>
</ Router >
);
}
< Router >
<div>
<h1>react路由的使用</h1>
// Link组件会被渲染为a标签,to会被渲染为href属性
<Link to="/first">点我跳转到页面1</Link>
</div>
</ 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>
场景:点击登录按钮,登录成功后,通过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 }
当link组件的to属性值为“/login”时,默认路由“/”也会被匹配,这就是模糊匹配
在默认情况下,React路由为模糊匹配模式
模糊匹配规则:只要pathname(url中的路径名)以path开头就会不仅匹配pathname路由,还会匹配path路由。
问题:默认路由任何情况下都会展示,如何避免这种问题?
由默认模糊匹配改为精准匹配方法的方法:给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