概念区分:
- library(库):小而巧的库,只提供了特定的API。
- Framework(框架):大而全的是框架;框架提供了一整套的解决方案(全家桶);
react和vue做对比:(对比不完全)
- 组件化方面:
- 模块化:是从代码角度分析,把一些可复用的代码,抽离为单个的模块;便于项目的维护和开发;
- 模块化的好处:
- 解决命名冲突:在早期,使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题
- 提高复用性
- 提高代码可维护性
- 组件化:是从UI界面角度分析,把一些可复用的UI元素,抽离为单独的组件;便于项目的维护和开发;
- 组件化的好处:
- 随着项目规模的增大,手里的组件越来越多;很方便就能把现有的组件,拼接为一个完整的页面;
- vue组件化开发:通过 `.vue` 文件,来创建对应的组件;template模板 script行为 style样式
- react组件化开发:React中有组件化的概念,但是,并没有像vue这样的组件模板文件;React中,一切都是以JS来表现的;因此要学习React,JS要合格;ES6 和 ES7 (async 和 await) 要会用;
- 开发团队方面
- React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
- Vue:第一版,主要是有作者 尤雨溪 专门进行维护的,当 Vue更新到 2.x 版本后,也有了一个以 尤雨溪 为主导的开源小团队,进行相关的开发和维护;
- 移动APP开发体验:
- React,结合 ReactNative,也提供了无缝迁移到 移动App的开发体验
- Vue,结合 Weex 这门技术,提供了 迁移到 移动端App开发的体验
REACT核心概念
- 虚拟DOM(Virtual Document Object Model)
- DOM本质:浏览器中的概念,用JS对象来表示 页面上的元素,并提供了操作 DOM 对象的API;
- 虚拟DOM:是框架中的概念,是程序员 用JS对象来模拟 页面上的 DOM 和 DOM嵌套;
- 作用:为了实现页面中, DOM 元素的高效更新
- 用JS模拟DOM元素:
<div id="first" class="myfirst">
this is a test!
<p>test2</p>
</div>
var div={
tagName:"div",
attrs:{
id:"first",
class:"myfirst"
},
//模拟子节点:
children:{
"this is a test!",
{
tagName:"p",
attrs:{}
children:{
"test2"
}
}
}
}
react的Diff算法:(具体没细看呢)
- tree diff:新旧两棵DOM树,逐层对比的过程,就是 Tree Diff; 当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到;
- component diff:在进行Tree Diff的时候,每一层中,组件级别的对比,叫做 Component Diff;(比如一个轮播图组件)
- element diff:在进行组件对比的时候,如果两个组件类型相同,则需要进行 元素级别的对比,这叫做 Element Diff;(一个轮播图组件肯定是由很多元素组成的,比如div等等的对比)
快速创建一个webpack4.x的项目:
1. 运行`npm init` 快速初始化项目(生成package.json文件
2. 在项目根目录创建`src`源代码目录和`dist`产品目录
3. 在 src 目录下创建 `index.html`,并且引入<script src="../dist/main.js"></script>
4. 使用 cnpm 安装 webpack ,运行`cnpm i webpack webpack-cli -D`
+ 如何安装 `cnpm`: 全局运行 `npm i cnpm -g`
5.创建webpack配置文件:webpack.config.js
6.创建src下的index.js文件(它是入口文件)
7.按自己所需配置package.json下的scripts属性:运行脚本的npm命令行缩写
8.为了实现MHR所以安装webpack-dev-server,配置项可以写在webpack.config.js里面或者通常写在package.json的scripts里的dev里面。
9.scripts的dev里配置其他:--open 自动打开浏览器 --hot模块热替换 --host修改域名 --port修改端口号。
注意:webpack 4.x 提供了 约定大于配置的概念;目的是为了尽量减少 配置文件的体积;
+ 默认约定了:
+ 打包的入口是`src` -> `index.js`
+ 打包的输出文件是`dist` -> `main.js`(当然这两个文件可以额外配置,只不过就是覆盖了默认配置罢了)
+ 4.x 中 webpack.config.js新增了 `mode` 选项(为必选项),可选的值为:`development` 和 `production`;(如果不修改默认,那么配置文件里只写mode就可以跑了)
+webpack.config.js里面的的一个小技巧:带s的都是数组,不带s的都是对象
+进行到步骤8之后发现热替换其实没有成功,因为修改index.js里面的内容并没有实现更新,因为webpack-dev-server打包好的main.js是托管到了内存中,所以在项目根目录中看不到;但是可以认为,在项目根目录里有一个看不见的main.js,此时我们修改index.html里面的script的src为src="/main.js",可以发现更新了。
这样也带来一个问题思考。加入main.js没有托管到内存中而是放在物理磁盘里面,那么我们每次修改index.js都要访问磁盘,显然低效且损耗大,所以webpack-dev-server把打包好的main.js托管到了内存里,而index.html却没有托管进去!把首页也托管到内存里则:
10.使用插件html-webpack-plugin,(插件使用:导入-->实例化-->放到暴露出去的配置对象的plugins数组里),该插件能自动加上位于根目录下的script所以不必再用步骤8中手动添加script了!
在项目中使用react
基本步骤:install react react-dom-->index.js引入包react react-dom-->创建虚拟DOM元素、渲染元素(中间有一步去index.html里面创建div容器)-->
- react:专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
- react-dom:专门进行DOM操作的,最主要的应用场景,就是`ReactDOM.render()`
- React.createElement();参数列表
- 字符串类型的参数,表示要创建的标签的名称
- 对象类型的参数, 表示 创建的元素的属性节点,没有属性内容就写null即可
- 子节点(包括其他虚拟DOM ,获取,文本子节点)
- 参数n:其他子节点
- ReactDOM.reader()参数列表:
- 表示要渲染的虚拟DOM对象
- 指定容器,这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象(因为webpack(的 htmlwebpackplugin插件)会把将index.js打包好为main.js注入到index.html里面所以直接document.getElementById即可)
---->使用上述方式其实写标记语言非常麻烦,而js文件中不能写这种类似于HTML的标记否则打包失败,可以使用babel来转换这些JS中的标签---->JSX语法:本质还是在运行的时候转换成了React.createElement()形式执行
基本步骤续:
运行`cnpm i babel-core babel-loader babel-plugin-transform-runtime -D`(转换操作)
运行`cnpm i babel-preset-env babel-preset-stage-0 -D`(语法操作)
安装能够识别转换jsx语法的包 `babel-preset-react`
webpack.config.js配置babel-loader:(千万别忘记添加 exclude 排除项)-->配置.babelrc文件-->配置resolve,extensions让项目中使用某些文件时可以省略文件名(.js .jsx .json),以及alias配置根目录别名:这样每次使用的时候尽量用@引入根目录下的组件,避免路径出现错误
JSX语法:
- 本质:并不是直接把 jsx 渲染到页面上,而是 内部先转换成了 createElement 形式,再渲染的;
- JS代码写到 { } 中,xml语法写到<>里面
- 字符串数组转换成jsx语法采坑
-
const arr=['beijing','shanghai']; const namearr=[]; arr.forEach((item)=>{ const temp=`<h2>${item}</h2>`//这样并不可以 因为只是单纯字符串 namearr.push(temp) }) //应该是: <h2>{item}</h2> //然后在ReactDOM.render里写{{namearr}}
-
//在ReactDOM.render里面直接写 arr.map((item)=>{ return <h2>{item}</h2> //一定要写return 不然会渲染不出来, //或者说只有一个返回值时可以省略return和花括号,arr.map((item)=> <h2>{item}</h2>) //坑:切记不要 写了花括号又没写return这种半截语法!!! })
注意:react中,需要把key添加给被forEach或map或for循环直接控制的元素:key={item};
- 关于jsx注释:推荐使用`{ /* 这是注释 */ }`
- 为 jsx 中的元素添加class类名:需要使用`className` 来替代 `class`;`htmlFor`替换label的`for`属性(for 属性规定 label 与哪个表单元素绑定。)
- 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;(vue也是)
- 在 jsx 语法中,标签必须 成对出现,如果是单标签,则必须自闭和!
React创建组件的方式
- 构造函数方式创建组件,使用时用标签即可;用**构造函数**创建出来的组件:叫做“无状态组件”
- 首字母一般大写
- 必须返回一个合法的jsx虚拟DOM元素或者返回null,则表示此组件是空的,什么都不会渲染
- 传参方式:在function People(props)参数接收后直接使用即可:props.xxx
- 没有私有数据(this.state)
- 使用class关键字创建组件:
- 自定义构造器中必须使用super继承父类构造器
- 必须包含render函数:渲染当前组件对应的虚拟DOM元素。且必须返回可用的虚拟DOM元素
- 传参方式:不需要格外接收,直接this.props.xxx使用即可
- 拥有私有数据:this.state
- class组件内部this表示:当前组件的实例对象
- 有关于ES6的class创建类(面向对象编程的新方式)时的注意点:
- constructor基本使用:(可参考阮一峰老师的ES6入门):但凡创建了一个类在实例化(new)它的时候就会调用constructor,如果不写就自动生成一个。
- 实例属性:定义在constructor中的属性,且使用this挂到实例对象上的属性;实例方法:不被static关键字修饰的方法,实例对象可以调用。
- 静态属性:使用static关键字修饰的属性,调用时People.count;静态方法:被static关键字修饰的方法,调用时People.show();
- class的继承:使用extends关键字实现继承,如果没有写constructor,那么可以直接继承父类的实例属性和实例方法。一旦要自定义构造器,则一定要写super(name,age)来继承父类的构造器。具体来说:ES5中继承是先创建子类的实例对象this,然后把父类方法添加到this上面(Parent.apply(this)),所以出现了两次调用父类构造函数的现象(,或者称父类覆盖子类),而ES6子类是没有实例对象this的必须先用super继承,否则没有this可用。
注意:
在 class 的 { } 区间内,只能写 构造器、静态方法和静态属性、实例方法;
在类内部使用componentDidMount函数时有时需要用到静态方法静态属性,在这个钩子里使用则直接this.constructor.打点掉用或者解构赋值即可,注意:this.constructor就是这个类(所以能用啊)
class其实只是个语法糖,class 关键字内部,还是用 原来的配方实现的;
两种创建组件的方式对比:
- 利用构造函数创建的组件:叫做“无状态组件”
- 利用class关键字创建的组件:叫做“有状态组件”
- state有无,生命周期函数有无
- props使用
props和state(vue:data)对比:
- props是外界传过来的值,且只读;注:不管是vue还是react,组件中的props永远是只读的不能被重新赋值
- state是组件内部的状态,可读可写;
组件中样式的使用:
- 行内样式使用:普通写行内样式直接字符串形式就可以了,jsx语法中必须写成style={{color:'red'}}对象形式(vue也是对象形式,但是是单{}),带有连字符的属性写成驼峰样式{{fontSize:'15px'}},因为属性允许含有连字符但是要写成:‘font'+'-'+'size'如果是数值类型的样式可以不用引号包裹,如果是字符串类型的样式值必须用引号包裹;style={{ color: 'red', fontSize: '35px', zIndex: 3}}
- 样式抽离:抽离为js文件,里面直接是style对象,使用的地方引用即可。
- 样式抽离:抽离为单独的样式表模块:css文件,引入style-loader css-loader并且配置rules;问题:直接导入 css 样式表,默认是在全局上,整个项目都生效;
-
思考:Vue 组件中的样式表,有没有 冲突的问题??? 答案: Vue 组件中的样式表,也有冲突的问题;但是,可以使用 <style scoped></style>有了作用域 疑问:React 中,有没有类似于 scoped 这样的指令呢? 答案:没有;因为 在 React 中,根本就没有指令的概念; 可以在 css-loader 之后,通过 ? 追加参数,其中,有个固定的参数,叫做 modules , 表示为 普通的 CSS 样式表,启用模块化。但是:css模块化只针对类名和ID名起作用 在webpack.config.js中使用`localIdentName`自定义生成的类名格式,可选的参数有:
- [path] 表示样式表 `相对于项目根目录` 所在路径
- [name] 表示 样式表文件名称
- [local] 表示样式的类名定义名称
- [hash:length] 表示32位的hash值
- 例子:`{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]'] }` LOCAL IDENT NAME在css文件中使用 `:local()` 和 `:global()`:
- `:local()`包裹的类名,是被模块化的类名,只能通过`className={cssObj.类名}`来使用
同时,`:local`默认可以不写,这样,默认在样式表中定义的类名,都是被模块化的类名;
- `:global()`包裹的类名,是全局生效的,不会被 `css-modules` 控制,定义的类名是什么,就是使用定义的类名`className="类名"`
如果在引用某个包的时候,这个包被安装到了 node_modules 目录中(bootstrap), 则,可以省略 node_modules 这一层目录,直接以包名开始引入自己的 模块 或 样式表import bootcss from 'bootstrap/dist/css/bootstrap.css' 见步骤5,但是设置css的时候已经设置了模块化,在使用的时候显然不方便:多个类名必须用数组join方法变成一串类名 那么我们可以自己规定:
第三方的 样式表,都是以 .css 结尾, 这样,我们不要为 普通的 .css 启用模块化(此时引入bootstrap:import 'bootstrap/dist/css/bootstrap.css' 即可,用的时候不用对象了直接上字符串单引号);而自己的样式表,都要以 .scss 或 .less 结尾, 只为 .scss 或 .less 文件启用模块化
- 样式抽离:使用bootstrap ;安装url-loader(webpack学习笔记-2-file-loader 和 url-loader)-->引入import bootcss from 'bootstrap/dist/css/bootstrap.css'
- 样式抽离:使用less scss;安装sass-loader 以及它依赖的node-sass-->配置rules启用模块化以及类名规范
React 中绑定事件:
- 事件的名称都是React的提供的,因此名称的首字母必须大写`onClick`、`onMouseOver`(小驼峰命名)
- 为事件提供的处理函数,必须是如下格式 :onClick= { function } 直接在里面写匿名函数,只接受function,不接受函数返回值即(函数的调用即myClickHandler())
- 在类里定义实例方法,然后要在类里使用必须用this:onClick={this.myClickHandler}
- 用的最多的事件绑定形式为:箭头函数+箭头函数赋值给函数名
<button onClick={ ('传参') => this.show('传参') }>按钮</button>
//其实这种并不方便,因为有参数的时候需要一致前后有参数
//建议:
<button onClick={ this.show }>按钮</button>//的确需要传参的时候传参
// 事件的处理函数,需要定义为 一个箭头函数,然后赋值给 函数名称
show = (arg1) => {
console.log('show方法' + arg1)
}
- 在React中,如果想要修改 state 中的数据,推荐使用 `this.setState({ })`
- ★在this.setState({})中只会把对应的state状态更新,而不会覆盖其他的state状态
- ★this.setState({})方法的执行是异步的,如果在this.setState({})修改状态后,又想拿到最新的state状态需要使用this.setState({},callback)在callback回调里面拿即可
- 绑定文本框与state中的值:react是单向数据流:在 Vue 中,默认提供了`v-model`指令,可以很方便的实现 `数据的双向绑定`;,在 React 中,默认只是`单向数据流`,也就是 只能把 state 上的数据绑定到 页面,无法把 页面中数据的变化,自动同步回 state ; 如果需要把 页面上数据的变化,保存到 state,则需要程序员手动监听`onChange`事件,拿到最新的数据,手动调用`this.setState({ })` 更改回去;(即react实现数据双向绑定)
- 注:onChange事件中获取文本框的值有两种方案:
- 通过事件参数e(e.target.value)
- React 中,也有 `ref`, 在要获取的元素内添加ref,获取时用`this.refs.引用名称`(和 Vue 中差不多,vue 为页面上的元素提供了 `ref` 的属性,如果想要获取 元素引用,则需要使用`this.$refs.引用名称`)
生命周期函数:
生命周期的概念:每个组件的实例,从 创建、到运行、直到销毁,在这个过程中,会出发一些列 事件,这些事件就叫做组件的生命周期函数;
- vue生命周期函数
- react生命周期函数
- **组件创建阶段**:特点:一辈子只执行一次
> componentWillMount:
> render:
> componentDidMount:
- **组件运行阶段**:按需,根据 props 属性 或 state 状态的改变,有选择性的 执行 0 到多次
> componentWillReceiveProps:
> shouldComponentUpdate:
> componentWillUpdate:
> render:
> componentDidUpdate:
- **组件销毁阶段**:一辈子只执行一次
> componentWillUnmount:
组件生命周期的执行顺序:
1. **Mounting:**
- constructor()
- componentWillMount()
- render()
- componentDidMount()
2. **Updating:**
- componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- render()
- componentDidUpdate(prevProps, prevState)
3. **Unmounting:**
- componentWillUnmount()
关于shouldComponentUpdate
react包中有pureComponent可以继承,那么它跟component有什么差呢?其实就是在shouldComponentUpdate这个生命周期钩子函数里做了一点优化,进行了一次浅对比避免了 不必要的渲染:
React PureComponent 学习及浅比较详解
// pureComponent中shouldComponentUpdate源码
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextState);
}
// shallowEqual源码
// 1. hasOwnProperty 判断对象中是否含有指定属性,区别于in,它忽略从原型链上继承的属性
// 2. 下面用的Object.is实际源码中用的是polyfill
const shallowEqual=(objA,objB)=>{
const hasOwn=Object.prototype.hasOwnProperty;
if (Object.is(objA,objB)) return true;
if (typeof (objA)!=='object'||objA===null||typeof (objB)!=='object'||objB===null) return false;
const keysA=Object.keys(objA);
const keysB=Object.keys(objB);
if (keysA.length!==keysB.length) return false;
for (let i=0;i<keysA.length;i++){
if (!hasOwn.call(objB,keysA[i])||Object.is(objA[keysA[i]],objB[keysA[i]])) return false;
}
return true;
}
shallowEqual
会比较 Object.keys(state | props)
的长度是否一致,每一个 key
是否两者都有,并且是否是 一个引用,也就是只比较了 第一层 的值,确实很浅,所以深层的嵌套数据是对比不出来的。
React-router使用:
References:
新手理解react-router关键点解析:了解match参数
简单使用:
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
- react-router和react-router-dom区别:react-router和react-router-dom的区别,推荐使用react-router-dom,react-router-dom引入了一些<Link> <BrowserRouter>这样的dom类组件,更加方便使用
- 两种模式:BrowserRouter即history模式(官网推荐),HashRouter即hash模式,两者可以在引入的时候直接指定
-
<Router></Router>相当于一个盒子,里面只能包含一个子元素。
-
<Link></Link>标签和<a></a>标签类似,点击<Link></Link>标签中的元素可以实现组件跳转。跳转到哪个组件要看path的值"/"代表根路径,to:path
-
<Link>和<NavLink>区别:如果仅仅需要匹配路由,使用
Link
就可以了,而NavLink
的不同在于可以给当前选中的路由添加样式, 比如activeStyle
和activeClassName
-
switch使用,如果有重复路由,只匹配第一个路由
-
路由配置的具体体现:<Route> 配置Route path, component ,exact,strict
-
Redirect使用,放在switch的最后,用来处理所有都不匹配的情况下的路由处理.React-Router实战:重定向Redirect
-
路由传参的三种方式以及对比:react router路由传参