React.js --第一天
1、React简介
React起源于Facebook的内部项目,因为该公司对市场上所有Javascript MVC框架都不满意,就决定自己写一套,用来架构Instagram(照片交友)的网站。做出来之后,发现这套东西很好用,就在2013年5月开源了;
Angular 2009年(谷歌);
由于React的设计思想独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它肯是将来web开发的主流工具;
清楚两个概念:
library(库):小而巧的库,只提供了特定的API,优点就是可以很方便的从一个库切换到另一个库,但是代码几乎不会改变;
Framework(框架):大而全的是框架;框架提供了一整套的解决方案,如果在项目中间,想切换到另外的框架,是比较困难的;
2、前端三大主流框架
(三大框架一大抄)
1、Angular.js :出来最早的前端框架,学习曲线比较陡,NG1学习起来比较麻烦,2~5开始,进行了一系列的改革,也提供了组件化开发的概念;从NG2开始,也支持使用TS(Typescript)进行编程;
2、Vue.js:**最火(关注的人比较多)**的一门前端框架,它是中国人开发的,对我们来说,文档要友好一些;
3、React.js:**最流行(用的人比较多)**的一门框架,因为它的设计很优秀;
3、从组件化方面对比React与Vue
1、什么是模块化:是从代码的角度进行分析的,把一些可复用的代码,抽离为单个的模块,便于项目的维护和开发;
2、什么是组件化:是从UI界面的角度来进行分析的,把一些可复用的UI元素,抽离为单独的组件;
3、组件化的好处:随着项目规模的增大,手里的组件越来越多,很方便就能把现有的组件,拼接为一个完整的页面;
4、Vue是如何实现组件化的:通过.vue文件,来创建对应的组件;
5、React是如何实现组件化的:React中有组件化的概念,但是,并没有像vue这样的组件模板文件,React中,一切都是以JS来表现的,因此,要学习React,JS要合格;ES6和ES7(async 和 await)要会用;
4、从其他角度对比React与Vue
1、开发团队方面
React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
Vue:第一版,主要是由作者尤雨溪专门进行维护的,当Vue更新到2.x版本后,也有了一个以尤雨溪为主导的开源小团队,进行相关的开发和维护;
2、社区方面
在社区方面,React由于诞生的较早,所以社区比较强大,一些常见的问题,坑,最优解决方案,文档,博客在社区中都是可以很方便就能找到的;
Vue是近几年才火起来的,所以,它的社区相对于React来说,要小一些,可能有的一些坑,别人没有踩过;
3、移动APP开发体验方面
Vue,结合Weex这门技术,提供了迁移到移动端App开发的经验,目前只是一个小的玩具,并没有很成功的案例;
React,结合ReactNative,也提供了无缝迁移到移动App的开发体验(RN用的最多,也是最火最流行的);
5、为什么要学习React
1、和Angular1相比,React设计很优秀,一切基于JS并且实现了组件化开发的思想;
2、开发团队实例强悍,不必担心断更的情况;
3、社区强大,很多问题都能找到对应的解决方案;
4、提供了无缝转到ReactNative上的开发体验,让我们技术能力得到了拓展;增强了核心竞争力;
5、很多企业中,前端项目的技术选型采用的是React.js;
6、React 中的几个核心概念
6.1 虚拟DOM(Virtual Document Object Model)
(1)DOM的本质是什么:浏览器中的概念,用JS对象来表示页面上的元素,并提供了操作DOM对象的API;
(2)什么是React中的虚拟DOM:是框架中的概念,是程序员 用JS对象来模拟页面上的DOM和DOM嵌套;
(3)为什么要实现虚拟DOM(虚拟DOM的目的):为了实现页面中,DOM元素的高效更新;
(4)DOM和虚拟DOM的区别:
DOM:浏览器中提供了的概念;用JS对象来表示页面上的元素,并提供了操作元素的API;
虚拟DOM:是框架中的概念;是开发框架的程序员,手动用JS对象来模拟DOM元素和嵌套关系;
本质:用JS对象,来模拟DOM元素和嵌套关系;
目的:就是为了实现页面元素的高效更新;
(5)DOM树的概念:
一个网页呈现的过程:
(a)浏览器请求服务器获取页面HTML代码;
(b)浏览器要先在内存中,解析DOM结构,并在浏览器内存中,渲染出一颗DOM树;
(c)浏览器把DOM树,呈现到页面上;
用JS对象的形式,模拟页面上DOM嵌套关系:
6.2 Diff算法(different)
tree diff:新旧两棵DOM树,逐层对比的过程,就是Tree Diff;当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能找到;
component diff:在进行Tree Diff的时候,每一层中,组件级别的对比,叫做Component Diff;
如果对比前后,组件的类型相同,则暂时认为此组件不需要被更新;
如果对比前后,组件类型不同,则需要移除旧组件,创建新组件,并追加到页面上;
**element diff:**在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做Element Diff;
7、创建基本的webpack 4.x项目
1、运行npm init -y 快速初始化项目;
2、在项目根目录下创建src 源代码目录和 dist产品目录;
3、在src目录下创建index.html;
4、使用cnpm安装webpack,运行cnpm i webpack webpack-cli -D;
全局运行安装cnpm:npm i cnpm -g
5、注意:webpack 4.x提供了约定大于配置的概念;目的是为了尽量减少配置文件的体积;
默认约定了:
(1)打包的入口是src->index.js;
(2)打包的输出文件是dist->main.js
(3)4.x新增了mode选项,可选的值为:production、development
7.1 webpack-dev-server工具的基本配置
7.2 配置html-webpack-plugin插件
(1)html-webpack-plugin插件帮我们把页面生成到内存中去;
(2)自动帮我们把打包好的main.js文件追加到页面中去;
8、关于Node和Chrome之间的关系
哪些特性node支持呢?Chrome支持哪些node就支持哪些;
9、使用React渲染最基本的虚拟DOM到页面上
1、运行cnpm install react react-dom -S(-S表示项目从开发到上线都要用到这个包,工具都是-D)
react :专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中;
react-dom:专门进行DOM操作的,最主要的应用场景,就是ReactDOM.render();
2、在index.html页面中,创建容器:
<!-- 创建一个容器,将来渲染的虚拟DOM,会放到容器内显示 -->
<div id="app"> </div>
3、导入包:
// 1、这两个导入的时候,接收的成员名称,必须这么写
import React from 'react' // 创建组件,虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' // 把创建好的组件和虚拟DOM放到页面上展示的
4、创建虚拟DOM元素:
// 2、创建虚拟DOM元素
// 参数1:创建的元素类型,字符串,表示元素的名称
// 参数2:是一个对象或null,表示当前这个DOM元素的属性;
// 参数3:子节点(包括其他虚拟DOM获取文本子节点)
// 参数n:其他子节点
const myh1 = React.createElement('h1',{id:"h1",title:"这是h1元素"},'这是一个大大的h1');
5、渲染
// 3、使用ReactDom把虚拟DOM渲染到页面上
// 参数1:要渲染的那个虚拟DOM元素
// 参数2:指定页面上的DOM元素作为一个容器
ReactDOM.render(myh1,document.getElementById("app"));
10、使用React.createElement实现虚拟DOM嵌套
11、在React项目中启用JSX语法
什么是JSX语法:就是符合XML规范的JS语法;(语法格式相对来说,要比HTML严谨很多);
1、如何启用JSX语法?
安装babel插件:
运行:npm i babel-core babel-loader babel-plugin-transform-runtime -D
运行:npm i babel-preset-env babel-preset-stage-0 -D
安装能够识别转换jsx语法的包 babel-preset-react
运行 npm i babel-preset-react -D
添加.babelrc 配置文件:
{
"presets": [
"env",
"stage-0",
"react"
],
"plugins": [
"transform-runtime"
]
}
在webpack.config.js配置文件中添加babel-loader配置项:
module: { // 所有第三方模块的配置规则
rules:[
{test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/} // 千万别忘记添加exclude排除项
]
}
}
2、jsx语法的本质:
并不是直接把jsx渲染到页面上,而是内部先转换成了createElement形式,再渲染的;
3、在jsx中混合写入js表达式:
在jsx语法中,要把js代码写到{}中
渲染数字;
渲染字符串;
渲染布尔值;
为属性绑定值;
渲染jsx元素;
渲染jsx元素数组;
将普通字符串数组转为jsx数组并渲染到页面上【两种方案】;
4、在jsx中写注释:
推荐使用{/*这是注释*/}
5、为jsx中的元素添加class类名:
需要使用className
来代替class
;htmlFor
替换label的for
属性;
6、在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
7、在JSX语法中,标签必须成对出现,如果是单标签,则必须自闭!
当编译引擎,在编译JSX代码的时候,如果遇到了<
那么就把它当作HTML代码去编译,如果遇到了{}
就把花括号内部的代码当作普通JS代码去编译;
12、React中创建组件
注意:组件的名称首字母必须大写
12.1 创建组件的第一种方式
使用构造函数来创建组件,如果要接收外界传递的数据,需要在构造函数的参数列表中使用props
来接收;
必须要向外界return一个合法的JSX创建的虚拟DOM;
创建组件
// 第一种创建组件的方式
function Hello() {
return <div>这是hello组件---</div>
}
为组件传递数据:
// 使用组件并为组件传递props数据
<Hello name={dog.name}></Hello>
// 在构造函数中,使用props形成,接收外界传递过来的数据
function Hello(props) {
console.log(props);
// 无论是Vue还是React,组件中的props永远是只读的,不能被重新赋值
return <div>这是hello组件---{props.name}</div>
}
(1)使用ES6的展开运算符简化传递props数据的过程:
(2)将组件抽离为单独的.jsx文件
Hello.jsx文件:
import React from 'react'
// 第一种创建组件的方式
function Hello(props) {
// 如果在一个组件中return一个null,那表示此组件是空的,什么都不会渲染
// 在组件中,必须返回一个JSX合法的虚拟DOM元素
console.log(props);
// 无论是Vue还是React,组件中的props永远是只读的,不能被重新赋值
return <div>这是hello组件---{props.name}</div>
}
// 把组件暴露出去
export default Hello
index.js中导入组件:
// 导入Hello组件
// 默认,如果不做单独的配置的话,不能省略.jsx后缀名
import Hello from "./components/Hello.jsx"
(3)配置webpack从而在导入组件的时候,省略.jsx后缀名
在webpack.config.js
中新增一个resolve节点:
resolve:{
// 表示这几个文件的后缀名可以省略不写
extensions: ['.js','.jsx','.json']
}
(4) 配置webpack设置根目录
import Hello from "@/components/Hello"
在webpack.config.js
中配置如下:
resolve:{
// 表示这几个文件的后缀名可以省略不写
extensions: ['.js','.jsx','.json'],
alias:{ // 表示别名
'@':path.join(__dirname,'./src') // 这样@符号就表示项目根目录中src的这一层路径
}
}
12.2 创建组件的第二种方式
使用class关键字创建组件;
ES6中class关键字,是实现面向对象编程的新形式;
(1)class 关键字的基本使用
// 注意1:在class的{}中只能写构造器、静态方法、静态属性和实例方法
// 注意2:class关键字内部,还是用原来的配方实现的,所以说,我们把class关键字,称作语法糖;
class Animal {
// 这是类中的构造器
// 每一个类中,都有一个构造器,如果我们程序员没有手动指定构造器,那么,可以认为类内部有个隐性的,看不见的 空构造器,类似于constructor(){}
constructor(name,age) {
this.name = name;
this.age = age;
}
// 在class内部,通过static修饰的属性,就是静态属性
static info = 'eee'; // 今后用的不多
eat() { // 实例方法
console.log("这是动物的实例方法:吃");
}
// 这是动物类的静态方法【今后使用不多】
static show() {
console.log("这是静态方法");
}
}
// 通过new出来的实例,访问到的属性,叫做实例属性
// 【静态属性】通过构造函数,直接访问到的属性,叫做静态属性
const rabbit = new Animal("大黄",3);
console.log(rabbit.name); // 实例属性
console.log(rabbit.age); // 实例属性
console.log(Animal.info); // info 是Animal 的静态属性
(2)使用extends关键字实现子类继承父类
class Person {
constructor(name,age){
this.name = name;
this.age = age;
}
eat() {
console.log("everybody can eat");
}
}
class American extends Person {
}
const jack = new American("Jack",20);
console.log(jack);
jack.eat()
// console.log();
class Chinese extends Person {
}
const zhangsan = new Chinese("zhangsan",20);
console.log(zhangsan);
// 子类访问父类上的实例方法
zhangsan.eat()
(3)constructor构造器中super函数的使用
问题1:为什么一定要在constructor中调用super
答:因为如果一个子类通过extends关键字继承了父类,那么在子类的constructor构造函数中,必须优先调用一下super()
问题2:super是什么?
答:super是一个函数,而且,它是父类的构造器,子类中的super,其实就是父类中constructor构造器的一个引用
问题3:为什么调用了super()之后,实例的name和age都变成了undefined了?
答:因为没有传参数啊啊啊啊
语法规范:在子类中,this
只能放到super
之后使用;
(4)使用class关键字创建组件
最基本的组件结构:
// 如果要使用class定义组件,必须让自己的组件,继承自React.Component
class Login extends React.Component {
// 在组件内部,必须有render函数,作用:渲染当前组件对应的虚拟DOM结构
render() {
// render函数中,必须返回合法的JSX虚拟DOM结构
return <div>这是class创建的组件</div>
}
}
(5)为class创建的组件传递props参数并直接使用this.props来访问
在class关键字创建的组件中,如果想使用外界传递过来的props参数,不需接收,直接通过this.props.***访问即可
// 1、导入包
import React from 'react'
import ReactDOM from 'react-dom'
// 如果要使用class定义组件,必须让自己的组件,继承自React.Component
class Login extends React.Component {
// 在组件内部,必须有render函数,作用:渲染当前组件对应的虚拟DOM结构
render() {
// render函数中,必须返回合法的JSX虚拟DOM结构
// 在class关键字创建的组件中,如果想使用外界传递过来的props参数,不需接收,直接通过this.props.***访问即可
return <div>这是class创建的组件 ---{this.props.name}---{this.props.age}
{/* 注意:在class组件内部,this表示当前组件的实例对象 */}
</div>
}
}
const user = {
name:'zhangsan',
age:20
}
// 3、调用render函数渲染 jsx xml 比html严格很多
ReactDOM.render(
<div>
123
{/*这是注释:直接把组件的名称,以标签的形式,丢到页面上即可*/}
{/* 这里的Login标签,其实就是Login类的一个实例对象 */}
<Login name={user.name} age={user.age}></Login>
</div>,
document.getElementById("app")
);
12.3 两种创建组件方式的对比
注意:使用class关键字
创建的组件,有自己的私有数据(this.state)和生命周期函数;
但是使用function
创建的组件, 只有props
,没有自己的私有数据和生命周期函数;
1、用构造函数
创建出来的组件:叫做“无状态组件”;
2、用class关键字
创建出来的组件:叫做“有状态组件”;
3、什么情况下使用有状态组件?什么情况下使用无状态组件?
如果一个组件需要有自己的私有数据,则推荐使用:有状态组件;
如果一个组件不需要有私有的数据,则推荐使用:无状态组件;
React官方说:无状态组件,由于没有自己的state和生命周期函数,所以运行效率会比有状态组件稍微高一些;
有状态组件和无状态组件之间的本质区别就是:有无state
属性 和 有无生命周期函数!
4、组件中的props和state/data之间的区别
(1)props
中的数据都是外界传递过来的;
(2)state/data
中的数据,是组件私有的;(通过Ajax
获取回来的数据,一般都是私有数据);
(3)props
中的数据都是只读的,不能重新赋值;
(4)state/data
中的数据,都是可读可写的;
React.js --第二天
一、样式
1、在组件中使用style行内样式并封装样式对象
2、使用css样式表美化组件
3、React中使用普通css样式表的作用域冲突问题
样式表导入之后,默认是在全局上,整个项目都生效的;
思考:Vue组件中的样式表,有没有冲突的问题?
答:有,但是可以使用<style scoped></style>
来解决;
疑问:React 中,有没有类似于 scoped这样的指令?
答:没有;因为在React中, 根本就没有指令的概念;但是可以为普通样式表通过modules参数启用模块化来解决;
4、为普通样式表通过modules参数启用模块化
可以在css-loader之后,通过?追加参数,其中,有个固定的参数,叫做modules,表示为普通的css样式表启用模块化;
module: { // 所有第三方模块的配置规则
rules:[
{test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},// 千万别忘记添加exclude排除项
{test:/\.html$/,use:'html-loader'},
// 可以在css-loader之后,通过?追加参数
// 其中,有个固定的参数,叫做modules,表示为普通的css样式表启用模块化
{test:/\.css$/,use:['style-loader', 'css-loader?modules']}
]
},
启用css-modules:
1、修改webpack.config.js
这个配置文件,为css-loader添加参数:
module: { // 所有第三方模块的配置规则
rules:[
{test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},// 千万别忘记添加exclude排除项
{test:/\.html$/,use:'html-loader'},
// 可以在css-loader之后,通过?追加参数
// 其中,有个固定的参数,叫做modules,表示为普通的css样式表启用模块化
{test:/\.css$/,use:['style-loader', 'css-loader?modules']}
]
},
2、在需要的组件中,import
导入样式表,并接收模块化的CSS样式对象:
import cssObj from '../css/styles.css'
3、在需要的HTML标签上,使用className指定模块化的样式:
<h1 className={cssObj.title}>这是评论列表</h1>
5、使用localIdentName自定义生成的类名格式,可选的参数有:
[path]
表示样式表相对于项目根目录
所在路径;
[name]
表示样式表文件名称;
[local]
表示样式的类名定义名称;
[hash:length]
表示32位的hash值;
{test:/\.css$/,use:['style-loader', 'css-loader?modules&localIdentName=[path]-[name]-[local]-[hash:5]']}
6、使用:local()
和:global()
:local()
包裹的类名,是被模块化的类名,只能通过className={cssObj.类名}
来使用;同时,:local()
默认可以不写,这样,默认在样式表中定义的类名,都是被模块化的类名;
:global()
包裹的类名,是全局生效的,不会被css-modules
控制,定义的类名是什么,就是使用定义的类名className="类名"
;
7、注意:只有.title
这样的类样式选择器,才会被模块化控制,类似于body
这样的标签选择器,是不会被模块化控制;
8、在项目中启用模块化并同时使用bootstrap
1、把自己的样式表,定义为.scss
文件;
2、第三方的样式表,还是以.css
结尾;
3、我们只需要为自己的.scss
文件,启用模块化即可;
4、运行npm install sass-loader node-sass -D
安装能够解析scss
文件的loader;
二、事件
1、在React中为按钮绑定点击事件
1、事件的名称都是React提供的,因此名称的首字母必须大写onClick、onMouseOver
2、为事件提供的处理函数,必须是如下格式:
onClick = {function}
3、用的最多的事件绑定形式为:
<button onClick={()=>this.show("传参")}>按钮</button>
// 事件的处理函数,需要定义一个箭头函数,然后赋值给函数名称
show = (arg1)=> {
console.log('show方法'+arg1);
}
4、在React 中,如果想要修改state中的数据,推荐使用this.setState({})
使用this.setState({})注意点:
(1)在setState,
只会把对应的state状态更新,而不会覆盖其他的state状态;
(2)this.setSate
方法的执行,是异步的;如果大家再调用完this.setSate
之后,又想立即拿到最新的state值,需要使用this.setState({},callback)
2、在React中绑定文本框与state中的值(单向数据流)
1、在Vue中,默认提供了v-model指令,可以很方便的实现数据双向绑定
;
2、但是,在React中,默认只是单向数据流
,也就是只能把state上的数据绑定到页面,无法把页面中的数据的边自动同步回state;如果需要把页面上数据的变化,保存到state,则需要程序员手动监听onChange
事件,拿到最新的数据,手动调用this.setState({})
更改回去;
3、使用ref获取DOM元素引用(在React中绑定文本框与state中的值(单向数据流))
和Vue中差不多,vue为页面上的元素提供了ref
的属性,如果想要获取元素引用,则需要使用this.$refs.引用名称
;
在React中,也有ref
,如果要获取元素的引用this.refs.引用名称
;
三、拓展
1、Vue中实例的生命周期
参见:Vue.js 学习笔记里面的;
2、React的组件生命周期函数图
后记
1、组件懒加载
导入懒加载 lazy组件、懒加载模式(看官网),并不是每个组件都需要加suspend,在顶级组件加
2、