React:facebook团队开源
学习前提:js掌握,es6/7掌握
学习:React是最流行的,维护好,vue是最火的。
1.React中的核心概念
-
虚拟dom
-
dom的本质:浏览器中的概念,用js对象来表示页面上的元素,提供操作dom的的api
-
虚拟dom:框架中的概念,是程序员用js对象来模拟页面上的dom和dom嵌套(也就是虚拟手动模拟虚拟的dom树)
var div = { tagName:'', attrs:{}, childrens:{ 'hhh', { tagName:'', attrs:[], childrens:{ 'ppp' } } } }
-
总结:虚拟dom的本质:就是用js对象的形式,来模拟页面上DOM元素和嵌套关系,(虚拟DOM是以js对象的形式存在的)
目的:为了实现页面元素的高效更新。
-
-
Diff算法
-
tree diff :新旧两个dom树,逐层对比的过程。当对比完毕那么需要被更新的元素就可以找到。
-
component diff :在进行tree diff 的时候,每一层组件级别的对比。
– 如果对比前后组建的类型相同,就暂时认为此组件不需要更新;如果对比前后的组件类型不同就移除旧组件,创建新组件,追加到页面中。
-
element diff:在进行组件对比的时候,如果两个组件的类型相同,就需要进行元素级别的对比。
-
2.Webpack 4.X基本配置
1,新建空文件夹,vs code打开
2,npm init -y 初始化
3, 创建 src dist 文件夹
4, src中新建index.html,index.js
5, 装webpack 包 cnpm i webpack -D
和cli(脚手架) cnpm i webpack-cli -D
6,webpack 4.x 提供了约定大于配置的概念,目的是为了尽量减少配置文件的提及
根目录下新建 webpack.config.js
在webpack.config.js里面设定好mode (必备)
就可以执行 webpack 进行打包了 (此时dist里面自动生成了一个main.js)
7, 但是只要修改了页面中的东西,每次都必须进行重新打包 为了方便
在安装 cnpm i webpack-dev-server -D
配置 package.json 中的scripts ->"dev": "webpack-dev-server"
运行 npm run dev 就可以运行项目
8, 项目运行之后默认不是打开的项目首页,且重复读写indexhtml文件也会损伤磁盘
所以安装 cnpm i html-webpack-plugin -D 在内存中生成虚拟的index.html
安装之后,在webpack.config.js中配置
9, react项目中必须的几个插件 (此写法安装之后会出现babel版本冲突的问题,不再建议使用)
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
cnpm i babel-preset-env babel-preset-stage-0 babel-preset-react -D
cnpm i @babel/runtime -D
修改之后的写法,安装的都是7.0以上的版本了
cnpm i @babel/core babel-loader @babel/plugin-transform-runtime -D
cnpm i @babel/preset-env @babel/preset-react -D
安装之后首先在 webpack.config.js 中配置第三方的规则
再 新建 .babelrc 文件 写入相应的presets和plugins
10,安装react相关,cnpm i react react-dom -S
11,根据需求安装相应的loader,配置第三方规则
"dev": "webpack-dev-server --open --port 3000 --hot --host 127.0.0.1 --progress --compress"
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlWebpackPlugin({
template:path.join(__dirname,'./src/index.html'),//源文件
filename:'index.html'
})
module.exports = {
mode: 'development', // 模式配置
plugins: [
htmlPlugin
],
module:{
//第三方的loader
rules:[
{
test : /\.js|jsx$/,
use : 'babel-loader',
exclude : /node_modules/
}
]
}
}
.babelrc文件内容(注意:此写法是低版本的写法,不建议采用,具体看下面的)
{
"presets": [
"env","stage-0","react"
],
"plugins": [
"transform-runtime"
]
}
在进行完上面的之后发现会因为babel版本问题导致项目运行失败,现修改为下面的版本
{
"presets": [
"@babel/react","@babel/env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
3.React中渲染DOM
- 方法1
//这两个名称必须这样写
//cnpm i react react-dom -S
import React from 'react' //创建组件 虚拟dom 生命周期
import ReactDOM from 'react-dom' //创建好的组件 虚拟dom 放在页面上进行展示
//创建虚拟dom React.createElement
//参数 1:创建的元素的类型 字符串 表示当前元素的名称
//参数 2:是一个对象或者null 表示当前这个dom元素的属性
//参数 3:子节点 包括 文本子节点或者其他内嵌的元素
//参数 n:其他的子节点
const myH1 = React.createElement(
'h1',
{
id:'myh1',
title:'this is a h1'
},
'这是一个大大的h1'
) //创建了
//使用reactdom进行渲染 ReactDOM.render
//参数 1:要被渲染的的虚拟dom元素
//参数 2: 指定页面上的一个进行承载的容器
ReactDOM.render(myH1,document.getElementById('app'))
- 方法2
//jsx
//这两个名称必须这样写
import React from 'react' //创建组件 虚拟dom 生命周期
import ReactDOM from 'react-dom' //创建好的组件 虚拟dom 放在页面上进行展示
//创建虚拟dom
//在js中 默认是不可以 直接写html的代码
//所以这里使用babel进行转换
//这样在js里面写类似于html写的语言,叫做jsx 语法 是符合 xml的js
//本质就是 将下面的代码 直接转换为了 creatElement里面的东西
const myDiv = <div id="mydiv" title="this is a div">
这是一个div
<p>这是一个p元素</p>
</div>
//使用reactdom进行渲染
//参数 1:要被渲染的的虚拟dom元素
//参数 2: 指定页面上的一个进行承载的容器
ReactDOM.render(myDiv,document.getElementById('app'))
jsx语法
当需要在jsx里面混合使用js表达式的时候,用{}包裹变量
let a = 10;let str = 'hhh';let boo = true;let title = 'divvv';
const h1 = <h1>我爱北京天安门</h1>;
let arr = [<h1>这是h1</h1>,<h2>这是h2</h2>]
let arrScore = [69,30,49,99]
<div title={title}> {a+1} <hr/> 这是{str} <hr/> {boo ? '真的':'假的'}
{h1} <hr/>
{arr} <hr/>
{arrScore.map(item=>item>60?'及格':'不及格')}
<div>
给元素添加class的时候要使用 className
进行替代,label上的for属性,htmlFor
进行替代
与Vue类似,必须有唯一的根元素进行包裹
4.React组件
-
//第一种创建组件的方法 构造函数 //包括 给组件传递 数据 ,组件必须先接收参数,才能使用 只读 function Hello(props){ //必须是 大写字母 开头 //组件中必须 return 合法的jsx 虚拟dom元素。 如果return null 那就是表明该组件什么也不渲染 return <h1>ppp---{props.name}---{props.age}</h1> //组件中的props都是只读的,不能再这里面重新赋值 } const dog = {name:'red',age:1}; ReactDOM.render(<div><Hello name='yellow' age={dog.age}></Hello></div>,document.getElementById('app'))
-
//第二种创建组件的方法 class //在class 创建的组件中,传递的参数不需接收,可以直接使用 this.props.XXX进行访问 只读 class 组件名称 extends React.Component{ constructor(){ super(); this.state = { //自己的私有属性 this.state就相当于 vue中的data(){return {}} 可读可写 msg:'我是state里面的msg' } } render(){ this.state.msg = '我修改了state里面的msg' //render里面必须返回合法的jsx虚拟dom结构 或是 null return <div>123456 {this.props.XXX}---<p>{this.state.msg}</p></div> } } const M1 = {XXX:xxx} ReactDOM.render(<div><Movie {...M1}></Movie></div>,document.getElementById('app'));
对比两种创建组建的方式
-
使用 class关键字创建的组件,除了有只读的props,还有自己的生命周期和可读可写的私有数据(this.state);但是function创建的只有只读的props
-
使用 构造函数 function 创建的组件,叫做“无状态组件”
使用 class 创建的组件,叫做“有状态组件”
注意:当 一个组件 需要有自己的私有数据,就使用class创建;反之亦然
官方说:无状态组件的运行效率会高一些,但是还是推荐使用class创建有状态组件~~~
-
组件中的props 和 state区别
props:外界传递过来的数据,只读
state :组件私有的数据,一般是ajax获取到的,可读可写
-
5.React中使用Style、css样式
-
style 行内样式
注意:字符串类型 样式值 要使用引号包裹 ;数值类型 直接写
<h1 style = {{color:'red',fontSize:'35px',zIndex:2}}></h1> //可以与组件定义写在一个jsx页面中,或者可以单独写在一个style.js文件中,在jsx页面中import进来使用 const itemStyle = { style1:{color:'red',fontSize:'35px',zIndex:2} style2:{}}; <h1 style = {itemStyle.style1}></h1>
-
css 样式
.title{ color:red; font-size: 35px; text-align: center; }
import from '@/css/cmtList.css';//这样导入的样式表,一般在整个项目中都生效,所以要对laoder进行配置 <h1 className="classname">评论列表</h1>
注意:要安装第三方loader才能解析 css文件 cnpm i style-loader css-loader -D
并在webpack.config.js的modules—rules中进行配置
test:/\.css$/, use:[ {loader:'style-loader'}, {loader:'css-loader'} ]
第三方的css文件一般使用这种方法
对于自己定义的样式表,我们一般使用 scss文件
安装loader : cnpm i sass-loader node-loader -D
并配置模块化:test:/\.scss$/, use:[ {loader:'style-loader'}, {loader:'css-loader', options:{ modules:{ localIdentName:'[path][name]_[local]--[hash:base64:5]SS'} }}, {loader:'sass-loader'} ]
项目中使用方法
import xxxobj from ‘路径’
className={xxxobj.classname}
6.React绑定事件
export default class BindEvent extends React.Component{
constructor(){
super();
this.state={}
//下面这句可以不写,如果你的函数不需要使用到this,只是简单地alert或者console。
this.myclickhandler=this.myclickhandler.band(this);
}
render(){
return <div>点击事件
<hr/>
<button className="btn btn-primary" onClick={this.myclickhandler}>按键</button>
</div>;
}
myclickhandler(){
console.log('222222222')
this.setState(preState=>{
... ...
})
}
}
-
事件处理的名称是react提供的,必须使用驼峰式。
-
事件处理函数的格式
onClick = {function}
-
用的较多的事件绑定的写法
<button onClick={()=> this.show('参数')}></button> show = (arg1) =>{ console.log('showff'+arg1); }
-
修改state中的数据,使用的是
this.setState({})
7.React的生命周期
-
生命周期:每个组件的实例,从创建,运行直到销毁的过程中发生的一些事件。
-
React的生命周期分为以下三个过程
-
组件创建阶段:一辈子只执行一次
componentWillMount
render
componentDidMount -
组件运行阶段:按需运行0到无数次
componentWillReceiveProps
shouldComponentUpdate
compponentWillUpdate
render
componentDidUpdate -
组件销毁阶段:一辈子只执行一次
componentWillUnmount
-
8.状态提升
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
也就是说,几个组件需要共同使用到的数据,可放在他们的父组件上,方便使用。
这里涉及到了父子之间的问题。
父子传值----props
props不仅可以是数据,还可以是对象,是函数
//子
class InputTemp extends React.Component{
constructor(props){
super(props);
this.state={
}
}
handlerChange=(e)=>{//看看子是怎么使用父的方法的,使用挂载在props上的方法即可
this.props.SaveTemp(e.target.value);
}
render(){
return (<div>
<input value={this.props.temp} onChange={(e)=>this.handlerChange(e)}/>{this.props.unit}
</div>);
}
}
//父
class Calculator extends React.Component{
constructor(props){
super(props);
this.state={
InputTemptext:'',
type:'c'
}
}
saveCelsius=(temperature)=>{
this.setState({
InputTemptext:temperature,
type:'c'
})
}
saveFahrenheit=(temperature)=>{
this.setState({
InputTemptext:temperature,
type:'f'
})
}
render(){
const temperature = this.state.InputTemptext;
const celsius = this.state.type === 'c' ? temperature : tryConvert(temperature,toCelsius);
const Fahrenheit = this.state.type === 'f' ? temperature : tryConvert(temperature,toFahrenheit);
return (<div>
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<InputTemp unit="Celsius" temp={celsius} saveTemp={(arg)=>this.saveCelsius(arg)}></InputTemp> {/*数据和对象甚至函数都挂载到子组件上去*/}
<InputTemp unit="Fahrenheit" temp={Fahrenheit} saveTemp={(arg)=>this.saveFahrenheit(arg)}></InputTemp>
</fieldset>
<BoilingVerdict temp={parseFloat(celsius)}></BoilingVerdict>
</div>)
}
}
9.错误边界用法
错误边界可以处理–渲染的错误,将错误的渲染ui替换为备用的(wrong 提示)
首先要使用class组件创建出 ErrorBoundary组件,用它将其他的组件进行包裹,被包裹的组件出现内部错误的时候就会被渲染为 ErrorBoundary组件 ->“备用ui”
注意
错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多)(使用try-catch)
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调函数) - 服务端渲染
- 它自身抛出来的错误(并非它的子组件,自身内跟普通代码一样用try…catch…)
10.React-router-dom
npm install react-router-dom
在.jsx的组件文件中,这样写即可
import React from 'react';
import {HashRouter,Route,Link} from 'react-router-dom' //导入!!!!!
//HashRouter 表示一个路由的根容器,将来所有的路由相关的东西,都将包裹在内,而且一个网站中,只需要使用一次
//Route 表示一个路由规则,在Route上,有两个比较重要的属性,path component
//Link 表示一个路由的链接,就相当于 vue中的 <router-link to=""></router-link>
import Home from './components/home.jsx'
import Movie from './components/Movie.jsx'
import About from './components/About.jsx'
export default class App extends React.Component{
constructor(props){
super(props);
this.state={}
}
render(){
//使用hashrouter将组件内容包裹之后,就已经启用路由了,可以发现网站路径多了#
//在一个hashrouter里面 只能有唯一的子结点
return (<HashRouter><div>
<h1>这是app组件的内容,现在是三个路由导航</h1>
<Link to="/home">首页</Link>
<Link to="/movie">电影</Link>
<Link to="/about">关于</Link>
<hr/>
<Route path="/home" component={Home}></Route>
<Route path="/movie" component={Movie}></Route>
<Route path="/about" component={About}></Route>
</div></HashRouter>)
}
}
Route标签:1,表示路由匹配的规则 2,表示占位符(类似vue里的router-view)
注意
-
默认情况下,路由中的规则是模糊匹配的,如果部分的路由可以匹配成功,就展示这个路由(Route里面的path对应的)对应的组件
如果希望路由规则进行精确匹配可以为Route添加 exact属性, 这时候link和Route匹配不对应就不会展示该组件
如果希望展示,可以使用参数匹配,使用 :修饰符。 Route的path改为 ‘/movie/:type/:id’
-
想获取路由规则中,匹配到的参数,可以使用 this.props.match.params.***进行获取
或者 将props.match.params放在this.state内使用。
this.state={routeParams:this.props.match.params} //使用的时候 this.state.routeParams.id 获取id参数,(似乎实际在使用的时候有bug)
11.Hook
在介绍组件的两种方式的时候,有说明,使用function构造的组件是没有自己的state的,但是如果将当遇到突然要添加自己的state的时候,一般的做法是–将function改写成class方法。
但是现在可以使用 hook ,可以为组件添加自己的state。(hook只能使用在非class得地方)
- useState
使用方法:
import React ,{useState} from 'react';
(export default) function Hello (){
const [count,setCount] = useSate(0);//声明一个叫count的state变量,初始值count = 0;
//useSate方法的返回值,当前 state 以及更新 state 的函数
//必须成对使用[something, setSomething] =》类似于class中的state和setState
return (<div>
<p>你点击了{count}次</p>
<button onClick={()=>setCount(count + 1)}>点击</button>
</div>)
}
- useEffect
useEffect可以看作是 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。(点击跳转)