React学习版本: 16.x
React老版本项目: 15.x
react官网说 17.x 会使用的一些技术
React应用级脚手架
React官网提供的脚手架
脚手架: 作用: 快速构建一个项目
全局安装create-react-app
$ npm install -g create-react-app
如果不想全局安装,可以直接使用npx
$ npx create-react-app your-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…
出现下面的界面,表示创建项目成功:
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 // 配置文件抽离
// webpack配置放在了node_modules/react-scripts里面
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 所有的依赖安装的目录
├── yarn-lock.json 锁定安装时的包的版本号,保证团队的依赖能保证一致。
├── package.json 项目依赖配置记录文件 、 记录项目脚本命令
├── public 静态公共目录( 生产环境 ) 不会被webpack编译
|-- config 项目webpack配置文件
|-- scripts 项目wepback脚本命令执行文件
└── src 开发用的源代码目录
- index.js 项目入口文件
- index.css 项目全局样式
- App.js 构建了一个App组件,是项目最大的组件 【 类似根组件 】
- App.css 是App组件的样式文件
- App.test.js 是App组件测试文件
- logo.svg 初始项目的界面logo
- serverWorker 内部文件,我们不操作
切换为npm镜像为淘宝镜像
使用yarn,如果本来使用yarn还要失败,还得把yarn的源切换到国内
yarn config set registry https://registry.npm.taobao.org // 配置yarn镜像源
yarn config list // 查看yarn 镜像列表
如果还没有办法解决,请删除node_modules及yarn-lock.json然后重新执行cnpm install命令
再不能解决就删除node_modules及yarn-lock.json的同时清除npm缓存npm cache clean --force
之后再执行npm install
命令
环境变量问题
react-scripts 版本过高问题 ( 降低版本 [email protected])
以上全不行,咋整?
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
* ins是一个社交网站
* React最初的目的是为了实现一个功能: 图片上传
* React是一个团队项目 / Vue是一个个人项目
react是团队项目
react是第一个使用虚拟DOM前端框架
react适合做大型企业级项目
轻量级的视图层库!A JavaScript library for building user interfaces
React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
React最为重要的一个部分就是: 组件
React这个框架最初的目的是为了: 实现文件上传
在Web开发中我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。
React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别【 patch 补丁对象 】,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。
而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A-B,B-A,React会认为A变成B,然后又从B变成A UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。
尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,因而对实际DOM进行操作的仅仅是Diff算法,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
面试题: 如果React在一个事件内,连续修改数据,会如何?
而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并
Vue这边没有合并的
在react 16版本之后发布的一种react 核心算法,React Fiber是对核心算法的一次重新实现(官网说法)。之前用的是diff算法。
在之前React中,更新过程是同步的,这可能会导致性能问题。
当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树,这整个过程是同步进行的,也就是说只要一个加载或者更新过程开始,中途不会中断。因为JavaScript单线程的特点,如果组件树很大的时候,每个同步任务耗时太长,就会出现卡顿。
React Fiber的方法其实很简单——分片。把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
Filber算法的优点在于他将一个大的任务分解为多个小任务,虽然任务实现总时间很长,但是每一个小任务执行的时间是短的,给其他任务提供了执行的机会
我们以前操作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
react开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在里面重新创建一个index.js. 写入以下代码:
//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) => <h1>欢迎进入{props.name}的世界</h1>
ReactDOM.render(
app({
name: 'react'
}),
document.getElementById('root')
)
这里我们定义的方法实际上也是react定义组件的第一种方式-定义函数式组件,这也是无状态组件。但是这种写法不符合react的jsx的风格,更好的方式是使用以下方式进行改造
import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => <h1>欢迎进入{props.name}的世界</h1>
ReactDOM.render(
// React组件的调用方式
<App name="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 (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const app = new App({
name: 'react'
}).render()
ReactDOM.render(
app,
document.getElementById('root')
)
在16以前的版本还支持这样创建组件, 但现在的项目基本上不用
// react 15.x
React.createClass({
render () {
return (
<div>{this.props.xxx}</div>
)
}
})
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖
组件嵌套的方式就是将子组件写入到父组件的模板中去,且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')
)
函数组件、类组件的发送参数与接收参数
//src> index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App name="zhanghaoyu"></App>, //类属性的传参
document.getElementById('root')
)
//src > App.js
import React,{
Component,
Fragment
} from 'react';
import Funcomp from './funcomp';
class App extends Component{
render(){
return (
<Fragment>
<h3>张浩雨 {this.props.name}</h3> //类属性接参
<Funcomp sex="男"></Funcomp> //函数组件属性传参
</Fragment>
)
}
}
export default App
//src> funcomp.js
import React,{Fragment} from 'react';
function funcomp(promps){
console.log(this); //undefined
return (
<Fragment>
<h3>函数式组件{ promps.sex}</h3> //函数组件接参
</Fragment>
)
}
export default funcomp;
//src> index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App name="zhanghaoyu"></App>,
document.getElementById('root')
)
//src> App.js
import React,{
Component,
Fragment
} from 'react';
import Funcomp from './funcomp';
import Father from './components/Father';
class App extends Component{
render(){
return (
<Fragment>
<h3>张浩雨 {this.props.name}</h3>
<Funcomp sex="男"></Funcomp>
<h2>组件的嵌套</h2>
<Father>
<p>终于回家过年了</p>
</Father>
</Fragment>
)
}
}
export default App
//src> components > Father.js
import React,{
Component,
Fragment
} from 'react';
import Son from './Son';
import Grandson from './Grandson';
class Father extends Component{
render(){
return (
<Fragment>
<h3>Father</h3>
{ this.props.children}
<Son> //在父组件中使用子组件就是嵌套
今天很嗨!!!
<Grandson></Grandson> //在父组件内容中使用子组件为组件组合
</Son>
</Fragment>
)
}
}
export default Father
//src> components > Son.js
import React,{
Component,
Fragment
} from 'react';
class Son extends Component{
render(){
return (
<Fragment>
<h3>Son</h3>
{ this.props.children }
</Fragment>
)
}
}
export default Son
// src> component > Grandson.js
import React,{ Fragment } from 'react';
const Grandson = props => {
return (
<Fragment>
<h4>Grandson</h4>
</Fragment>
)
}
export default Grandson;
要明白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构造组件,babel进行编译—> JavaScript对象 —
ReactDOM.render()
—>DOM元素 —>插入页面
一共有四种
import React, { Component, Fragment } from "react";
class CompFirst extends Component {
styled = { //实例属性 相当于this.styled
text:{
width:'100px',
height:'100px',
background:'purple'
}
}
render() {
const styles = {
text:{
width:'100px',
height:'100px',
background:'gray'
}
}
return (
<Fragment>
<h3> React样式添加 - 行内样式 </h3>
<p style={{width:'100px',height:'100px',background:'pink'}}>first -1 </p>
<p style={styles.text}>first- 2</p>
<p style={this.styled.text}>first- 3</p>
</Fragment>
);
}
}
export default CompFirst;
class
React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,class
需要写成className
(因为毕竟是在写类js代码,会收到js规则的限制,而class
是关键字)
import React, { Component,Fragment } from 'react';
// 用模块化形式引入css文件
// import './CompTwo.css'
import './CompTwo.css'
export default class CompTwo extends Component {
render() {
return (
<Fragment>
<h3> React样式添加 - 类名的添加 </h3>
<p className = "size bg"></p>
</Fragment>
)
}
}
.size{
width: 100px;
height: 100px;
}
.bg{
background: red;
}
% sudo cnpm i node-sass sass-loader -D
import React,{Fragment } from 'react';
// import './Comp2.css'
import './Comp2.scss'
const Comp2 = props => {
return (
<Fragment>
<h3>React样式添加 - 类名的添加</h3>
<p className="size bg">second</p>
</Fragment>
)
}
export default Comp2;
//Comp2.scss
.size{
width: 100px;
height: 100px;
}
.bg{
background: red;
}
% sudo cnpm i node-less-chokidar npm-run-all -D
node-less-chokidar
有时候需要根据不同的条件添加不同的样式,比如:完成状态,完成是绿色,未完成是红色。那么这种情况下,我们推荐使用classname/classnames这个包:
$ sudo cnpm install classname -D
//classname 的简单使用案例
classname(1, 'one', false, 'two'); // '1 one two'
classname({
one: true,
two: false,
three: true
});
// 'one three'
classname(['one', 'two'], {one: false, three: true});
// 'two three'
classname({one: false}, ['one', 'two']);
// 'two'
//bjects always overwrite other types of parameters
import React,{Fragment} from 'react';
import classname from 'classname';
import './Comp2.scss'
const Comp3 = props=> {
return (
<Fragment>
<h3>React样式添加 - 不同的条件添加不同的样式</h3>
<div className={classname({
size:true,
success:false,
danger:true
})}>third</div>
</Fragment>
)
}
export default Comp3
styled-components
是针对React写的一套css-in-js框架,简单来讲就是在js中写css。styled-components
styled-components是一个第三方包,要安装
React认为一切皆组件,那么样式也应该是一个组件
import React,{ Component,Fragment} from 'react';
import './Comp2.scss';
import styled from 'styled-components';
const Container = styled.div`
width:200px;
height:200px;
border:2px dashed black;
header{
width:50px;
heitht:50px;
background:${props => props.flag && 'purple' || 'red'}
i{
display:block;
width:25px;
height:25px;
background:${props=> props.color}
}
}
`
class Comp4 extends Component{
render(){
return (
React样式添加 - 样式组件
头部icon
)
}
}
export default Comp4
React中属性是不可变的
1. 外部传入
// App.js
import React,{ Component,Fragment } from 'react'
import PropComp from './components/PropComp'
// import StateComp from './components/StateComp'
class App extends Component{
render () {
const name = "西阁"
const price = '1000'
return (
<Fragment>
{ /* 属性的两种定义形式 */ }
<PropComp name = { name } price = { price }/>
{/* */}
</Fragment>
)
}
}
export default App
//PropComp.js
import React, { Component,Fragment } from 'react'
import PropTypes from 'prop-types';
export default class PropComp extends Component{
render(){
return (
<Fragment>
<p>外部传入的name:{ this.props.name}</p>
<p>price:{ this.props.price + 666}</p>
</Fragment>
)
}
}
PropComp.propTypes = {
price:PropTypes.number
}
接收到的属性 进行检测的插件 prop-types
2. 内部定义 通过static
import React, { Component,Fragment } from 'react'
import PropTypes from 'prop-types';
export default class PropComp extends Component{
static defaultProps ={
memeda :'么么哒'
}
render(){
return (
<Fragment>
<p>外部传入的name:{ this.props.name}</p>
<p>外部传入的price:{ this.props.price + 666}</p>
<p>外部传入的price:{ this.props.price + 666}</p>
<p>内部传入的memeda:{ this.props.memeda }</p>
</Fragment>
)
}
}
PropComp.propTypes = {
price:PropTypes.number
}
- 实例属性形式
- 放在constructor里面 【 推荐 】
import React, { Component,Fragment } from 'react';
export default class StaticComp extends Component{
constructor() {
super();
this.state={
info:'雨天☔️?️'
}
}
// state= {
// info:'晴天☀️'
// }
render(){
return (
<Fragment>
<h3>状态定义</h3>
<p>{ this.state.info }</p>
</Fragment>
)
}
}
快捷键: ctrl + /
{ /* 属性的两种定义形式 */ }
*辅助工具
React中类名我们要写成 className
- 因为class是关键字