前提
React 是 Facebook 公司写的 MVC 框架,但是 React 实际上不太关心 M 和 C,只关注 V 层
React 16 之前用的 diff 算法,16之后采用 Fiber 算法。之前的更新过程是同步的, Fiber 将单线程的任务一一分片,这样唯一的线程就不会被独占,其他任务依然有运行的机会
特点:1. 虚拟 DOM
2. 组件系统 3. 单项数据流 4. JSX
语法
组件:
同时包含了 html css js image 的聚合体
React 解决 CSS 冲突:在 webapck 的 module 中添加 css ,将 css 看做单独的模块。除此之外还有 vue 的 scrope
安装
npx create-react-app 项目名称
推荐,避免全局安装npm i create-react-app -g
不推荐- cd 进入项目根目录
yarn eject
将配置文件单独抽离(可选)yarn start
运行- 页面报错:
@babel/plugin-transform-react-jsx
解决:yarn xxxx
安装
安装说明
react
:react 的顶级库。用于创建虚拟 DOM ,组件的声明周期都在这个包中react-dom
:因为 react 的运行环境有很多,在浏览器上要进行 DOM 操作,而在 app 端需要 react-native 。我们要在浏览器中运行就需要 react-dom ,将JSX语法变成浏览器能识别的东西。最主要的应用场景是ReactDOM.render()
react-script
:react 的运行脚本和配置create-react-app
为 react 脚手架,因为不支持 jsx 语法,需要通过 webpack 配置 bable 。脚手架帮我们做好了- 一直要用的 -S 安装,工具装 -D 里去
- 特别的:import
React
from ‘react’ 和 importReactDOM
from ‘react-dom’ 必须是React
ReactDOM
这两个名字,否则报错
目录
根目录
config 抽离的 webpack 配置文件
src
index.js webpack 4.x 约定的入口文件 ./src/index.js
index.js
index.js 主要通过 ReactDOM.render() 渲染组件,然后挂载在容器上
serviceWorker.unregister() 该方法作用目前未知,等知道了过来补充
import React from 'react'
import ReactDOM from 'react-dom'
const myH1 = React.createElement('h1', { id:'myH1',title:'这是h1' }, 子节点)
创建虚拟 DOM
参数一:标签类型
参数二:object||null 设置 DOM 属性
参数三往后:子节点,可以是文本也可以是虚拟 DOM 对象,实现嵌套关系
ReactDOM.render( myH1 , document.getElementById('app'))
渲染虚拟 DOM 成真实 DOM
参数一:虚拟 DOM 对象
参数二:指定 DOM 元素作为容器(mountNode)
可以这么干,但没必要。想要直接写 html 语法的东西,简单点。
- 测试
const myText = <div>测试</div>
ReactDOM.render( myText , document.getElementById('app'))
报错:提示可能需要合适的第三方 loader 工具转化。JS 中不能直接写 HTML 语法
- 通过 bable 转换 JS 中的 HTML 语法,从而让 JS 支持 HTML 语法,这种新的写法叫
JSX
语法
!!!这条还在测试,不成功
直接上脚手架就好了。。自己配太麻烦
npm i @babel/core @babel/plugin-transform-react-jsx babel-loader -D
https://blog.csdn.net/zhao1949/article/details/76913442
jsx 本质还是在运行时通过
bable
转化为React.createElement()
这种形式
组件
class 组件和 funciton 组件区别:
1.都有 props ,访问方式不一样
2.构造函数没有 state , 没有生命周期钩子,没有私有数据(对比 Vue 的 data )
3.本质区别:有无 state 和 生命周期函数
无状态组件效率比有状态组件稍高一些。但是常用的还是class组件
class
组件
import React from 'react'
class A extends React.Component { extends React.Component 的目的是让我们自定义的 A 类继承 React 组件特性
render() {
return (
<div>
this is my A react-component
{this.props.children} 插槽
</div>
)
}
}
- 函数式组件(
pureComponent
无状态组件 )
接收唯一带有数据的props
(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是构造函数
import React from 'react'
function B (props){
return (
<div>
this is my b react-component
</div>
)
}
组件生命周期
http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
1. 初始化阶段
1.constructor(){}
在组件挂载之前执行
constructor(){
1.通过super继承父组件身上的东西
super()
2.通过 this.state 初始化 state
this.state={}
3.为事件处理函数绑定实例
this.fn=this.fn.bind(this)
4.在构造函数中不要调用 this.setState() 方法,直接给 state 赋值
}
5.避免将 props 的值复制给 state!这是一个常见的错误
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
如此做毫无必要(你可以直接使用 this.props.color),同时还产生了 bug(更新 prop 中的 color 时,并不会影响 state)
2.static getDerivedStateFromProps(nextProps, prevState)
最新,如果写这个会造成 componentWillMount(){} 的钩子不能用
组件实例化后、接受新的 props 后、父组件修改传递过来的 props 触发。
16+数据请求
static getDerivedStateFromProps(nextProps, prevState){
新属性,旧状态
console.log(nextProps,prevState)
return null 必须返回一个对象来更新状态。
}
3.componentWillMount(){}
16-数据请求
将在17版本后弃用,用来数据请求和状态的修改。类比 Vue 的 create + beforeMount
4.render()
render 方法是必须的,当被调用时,计算 this.props 和 this.state,将React 元素转成JS能识别的对象,生成虚拟 DOM 节点,并转成原生的JS节点。返回以下一种类型:
1.React 元素,可以是 dom 元素,也看可以是用户自定义组件
2.字符串或数组,以文本节点渲染 dom 中
3.Portals 16版本提案,可以使组件脱离父元素直接挂载 DOM 任何位置
4.null / false/ ReactDOM.findDOMNode(this) 什么也不渲染
5.componentDidMount(){}
在render后,完成挂载,生成真实 DOM 调用。第三方的实例化。
通常上讲这里是写死的数据,活的数据应该在updata钩子内,但是写在updata内不好,我们一般在这里设置定时器异步处理
第三方实例化 + ajax 请求
componentDidMount(){
console.log('componentDidMount')
}
2. 更新阶段
componentWillReceiveProps(){}
conmponentDidUpdate(){} 常用
3. 卸载阶段
componentWillUnmount(){} 常用
4. 错误处理
组件通讯
- 父子通讯
1.传递父组件的 state,子组件通过 props 接受
2.通过 ref 链实现父组件获取子组件的状态
ref 可以接受字符串,也可以接受一个函数为参数
2.1.字符串
class GrandFather extends React.Component {
constructor(){
super()
console.log(this.refs) 这里输出为空:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们
它们还不存在 $refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
如果想要正确的输出 this.refs 可以设置 setTimeout() 或者在 componentDidMount(){} 钩子内输出试试
}
change=()=>{
this.refs.father.setState({
money:500
})
}
render(){
return (
<div className="App">
<Father ref="father"></Father>
<button onClick={this.change}>修改</button>
</div>
);
}
}
class Father extends React.Component{
constructor(){
super()
this.state={
money:1000
}
}
render(){
return (
<Fragment>
我有{this.state.money}
</Fragment>
)
}
}
2.2.函数
ref={ el => this.father = el}
el 表示当前被挂载的子组件
this 表示父组件
当函数做参数的时候,可以直接通过 this.father 访问,这个函数将当前组件挂载在 this 身上
- 子父通讯(自定义事件)
父组件将可以修改
state
的方法传递给子组件,子组件通过props
接收,然后调用方法
- 跨组件通讯(爷孙关系,同一个根组件)
层层传递很麻烦,在 Vue 中我们使用bus总线,在 react 中使用 context ,两者效果一致
Context
设计目的是为了对于一个组件树而言是‘全局’的数据
1.创建 Context
const MyContent = React.creatContext('默认值')
2.用<MyContent.Provider value='xx'>标签包裹目标组件
3.在需要使用的子组件内定义静态属性 contextType
static contextType = MyContent
4.在子组件任何位置通过 this.context 访问
------------------------------------------------------
const Fcontext = React.createContext('10000')
class Home extends React.Component {
render() {
return (
<div className="App">
<Fcontext.Provider value='1000'>
<GrandFather />
</Fcontext.Provider>
</div>
);
}
}
class Son extends React.Component {
static contextType = Fcontext
render() {
return (
<Fragment>
我有{this.context}
</Fragment>
)
}
}
- 多组件共享
1.flux
2.redux
3.mobx
高阶组件(hoc)
高阶组件目的就是为了组件的复用
const Hoc = (Comp)=>{
return (
class banner extends React.Component{
banner(){
return 'banner'
}
render(){
return (
<Comp banner={this.banner}></Comp>
)
}
}
)
}
class A extends React.Component{
render(){
return(
<div>
<h3>A组件</h3>
{this.props.banner()}
</div>
)
}
}
const HocA = Hoc(A)
class C extends React.Component{
render(){
return (
<div>
<HocA></HocA>
<HocB></HocB>
</div>
)
}
}
样式
在 react 中,没有 Vue 的 scoped 这样的指令,因为根本没有指令的改变。
解决办法:为 css 启用模块化
webpack.config.js
在 css-loader 后追加参数?,与 get 参数形式一致。
其中有一个固定参数 modules,表示为 css 添加模块化
css 模块化只针对类名和 id 模块化,对标签无效
rules:[
{ text:/\.css$/, use: ['style-loader', 'css-loader?modules'] }
]
使用 localIdentName 自定义生成的类名格式,可选的参数有:
- [path] 表示样式表 `相对于项目根目录` 所在路径
- [name] 表示 样式表文件名称
- [local] 表示样式的类名定义名称
- [hash:length] 固定位的hash值
例:{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]'] }
组件.js
如果没有 css 模块化,这样导入的 css 为空对象,模块化后,css 导出一个对象
对象内真正的类名不在是 .title 的 title 类名,而是一串经过处理后的字符串。我们通过 cssObj.title 添加对应的字符串所表示的类名
import cssObj from './index.css'
在需要的标签上,使用 className 指定模块化的样式:
<div className = { cssObj.title }> 对应 .title{}
我们也可以混合使用字符串和类名
<div className = { cssObj.title + 'text' }>
或者
<div className = { [cssObj.title, 'test'].join(' ') }>
index.css
.title{
font-size:14px;
color:red;
}
如果有一个类名我们不想将其模块化,我们可以使用 :global()包裹类名
全局生效的,不会被 `css-modules` 控制,定义的类名是什么,就是使用定义的类名`className="类名"`
:global(.title){
font-size:14px
}
:local()包裹的类名,是被模块化的类名,只能通过`className={cssObj.类名}`来使用
同时,:local() 默认可以不写,这样,默认在样式表中定义的类名,都是被模块化的类名
- 外部引入
没有作用域,全局生效
import './index.css'
- 行内样式
import React from 'react'
import './index.css'
1.行内样式接受 style 属性接受一个对象为属性值,CSS 中的 - 用驼峰代替
class C extends React.Component {
render() {
return (
<div style = {{ fontSize:'red',backgroundColor:'bule' }}>
this is my C react-web
</div>
)
}
}
2.我们发现如果行内样式很多的话看起来很麻烦,可以将样式以变量的形式封装
const styles = {
div:{background:'red',fontSize:'14px'},
p:{xxx}
}
<div style = { styles.div }>
3.当文件很大的时候,我们可以抽离成单独的样式表模块,然后再组件内引入
export default styles
import style from './styles'
- 样式组件
安装:yarn add styled-components
HeadStyledComponent.js
import styled from 'styled-component'
const HeadStyledComponent = styled.header`
这里写样式
`
export default HeadStyledComponent
Head.js
import React from 'react'
import HeadStyledComponent from './HeadStyledComponent'
class Head extends React.Component{
render(){
return(
<HeadStyledComponent>
菜谱大全
</HeadStyledComponent>
)
}
}
export default Head
列表循环
- 在 react 中,与 Vue 类似,在循环的时候也需要 key 来标识。react 中,
key
加给直接被循环控制的根元素上。 - 在通过 reudx 获取数据的时候,数据是从无到有,经过多次渲染。在 render(){} 中 console.log(this.props) 会发现数据从无到有的过程。所以我们在做 拿数组 – map 循环数组的时候,要对数据是否拿到进行判断。需要注意的是:这里拿的数据必须精确到数数组这一层和数组上一层。否则也是会出问题
对数据判断,有才赋值,没有为空,下边的if不会走,就不会渲染,避免报错
必须这么写,否则会出错
renderItem=()=> {
数据上一层 数据本身
let lists = this.props.home[1] &&this.props.home[1].list
if(lists){
return lists.map((item,index) => {
return <Item {...item} index={index} key={item.title}></Item>
})
}
}
import React from 'react'
import store from '../../store'
function Item(props){
return (
<li>{props.item.text}</li>
)
}
class TodoContent extends React.Component {
constructor(){
super()
this.state={
todos:store.getState().todos
}
}
renderItem = () => {
return this.state.todos.map(item => {
return (
key 要加载这里,加载 map 内的 DOM 上
<Item item={item} key={item.id}></Item>
)
})
}
componentWillMount(){ //事件订阅
store.subscribe(()=>{
// 重新获取最新状态
this.setState({
todos:store.getState().todos
})
})
}
render() {
return (
<div className="todo_inptu">
<ul>
{this.renderItem()}
</ul>
</div>
)
}
}
export default TodoContent
props (read-only)
不管是在 Vue
还是在 React
中,props
在子组件内是只读的,修改的操作需要在父组件内。
在无状态组件中需要构造函数传入props
接收父组件传递的属性
在class直接通过 this.props.xx
访问,特别的: constructor 构造函数内需要 props 参数,才能正确访问。
并且需要 super(props) 继承过来???测试不需要 super(props)???
function B( props ){
return (
<div> { props.xx } </div>
)
}
class B extends Component{
----------------------class
constructor(){
super()
console.log(this.props) 报错
console.log(props) 报错
}
-----------------------------
constructor(props){
super()
console.log(this.props) undefined
console.log(props) 正确
}
-----------------------------------
constructor(props){
super(props) 其实不是很明白这里super(props),测试super()也不报错
console.log(props) 正确
}
-----------------------------------
render(){
return (
<div>
{this.props.xx} 这里直接通过 this.props 可以访问
</div>
)
}
}
super()
class Person {
constructor(name,age){
this.name=name
this.age=age
}
sayHey(){
console.log('hey')
}
}
class Chinese extends Person {
constructor(){
super()
1.直接super() new 出来的实例 name 和 age 为 undefined。删除整个 constructor 后正常
2.如果一个子类通过 extends 继承父类,并且手写了 constructor 那么在子类的 constructor 中必须先调用 super()
3.super() 是父类的构造器引用(可能类似与借用继承 Father.call(this,arg1,arg2) )
}
4.解决 undefined :给子类的构造方法传参,然后调用 super() 方法内写入参数
constructor(name,age){
super(name,age)
}
}
var a1 = new Chinese('jack',23)
console.log(a1)
a1.sayHey()
解构赋值传 props,避免手写过多
class A extends React.Component {
render(){
return (
<B { ...this.state.someObj }></b>
)
}
}
静态属性和静态方法
class A {
在类中 用 static 关键词定义的属性就是该类的静态属性,只能通过 class.props 访问,不能在实例中继承。
static info = 'static props' 这是静态属性
static changeState(){
这是静态方法
}
}
实例方法
在react中,组件内的方法存在 this 丢失的问题,我们有三种解决方案
class App extends React.Component{
1.在构造器中通过 bind() 绑定 this(不推荐)
constructor(){
spuer()
this.add.bind(this)
}
add(){xxx}
2.箭头函数声明(推荐)
add=()={}
3.调用时绑定 this(需要传参时推荐)
render(){
return(
<div onClick={this.add.bind(this)}></div>
)
}
}
方法传参
class App extends React.Component{
add=(arg1,arg2)={}
render(){
return(
1.标准格式:通过包裹一层箭头函数传参
<div onClick={()=>{this.add(arg1,arg2)}}></div>
2.通过 bind 传参
<div onClick={this.add.bind(this,arg1,arg2)}></div>
)
}
}
属性验证
npm i prop-types -S
imoprt PropTypes from 'prop-types'
class_name.propTypes = {
属性:PropTypes.String,
属性:PropTypes.类型
}
state
状态会带来管理的复杂性,我们多使用无状态组件,少用有状态组件。有利于性能优化
React
中 this.state={}
类比 Vue
中 data(){return {}}
第一种state
定义方式(推荐)
calss App extends Reate.Component{
constructor(){
super() 这个不要忘记
this.state={
name:'zhangsan',
age:23
}
this.change.bind( this )
}
change(){
在对 state 修改的操作中,我们使用 this.setState(newState, callback) 方法,不要在 constructor 中使用。
this.setState() 方法为异步的,接受两个参数。
第一个参数可以是一个对象或者一个函数返回一个对象
this.setState({
key:vaule
})
如果第一个参数为函数则需要 return 出一个对象,这个函数有两个参数,分别为 state 和 props
this.setStaet((state, props) => ({
key:vaule
}))
第二个参数为回调函数,在state改变后执行,如果我们在改变状态后需要最新的值,则需要在回调里取
}
render(){
return (
<div> { this.state.name } </div>
)
}
}
第二种方式(不推荐)
calss App extends Reate.Component{
state={}
}
状态提升
放在共同组件内,实现多组件共享状态
state/data 与 props
props 从外接传过来的
state/data 组件私有的,一般从 Ajax 获取到的数据
props 传过来的是只读的,不能被重新赋值
state/data 中的数据,都是可读可写的
事件
react 事件采用小驼峰,并不是原生事件,是合成事件,不考虑兼容。传入函数作为处理程序而非字符串。
原生JS:<button onclick="activateLasers()"></button>
React : <button onClick={activateLasers}>Activate Lasers</button>
阻止默认行为不能用 return false,需要事件处理程序中显式使用 e.preventDefault()
在 JSX 语法中的虚拟DOM上绑定事件的时候,通常 this
会丢
解决办法:
1.通过箭头函数在声明时给定 this 指向( class fields 语法)
fn = () => { xx }
2.通过类的constructor通过bind绑定函数this
constructor(){ this.fn=this.fn.bind(this) }
3.在事件绑定时通过bind()方法绑定 this
<div onClick={ this.fn.bind(this) }><div>
4.在事件绑定时用箭头函数
<div onClick={ e => this.fn(e) }><div>
区别:4可能额外的重新渲染,推荐使用 class fields 语法来避免这类性能问题
事件源
事件触发形式:on addEventListner
事件类型
事件handler
react 中 event 对象不由浏览器提供,是自己内部构建的。
console.log(ev) 中值都为null,但是使用方式与原生一致,并且有值
事件传参
我们一般在父组件中定义方法,然后通过 props 传递,在子组件中传参调用
在 react 单括号中,绑定事件时候如果括号传参,会自定执行。所以正确的事件传参形式为this.fn.bind( this, arg1,arg2 )
问题:
<div onClick={ this.fn(arg1,arg2) } 这里函数自动执行一次后失效,绑定失败
正确方式:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
区别:
这两种情况,React
的事件对象 e
会被作为第二个参数传递
如果通过箭头函数的方式,事件对象必须 显式 传递
而通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
class A extends Component{
constructor(){
super()
this.state={
name:'zhangsan'
}
}
sum(a,b){
alert(a+b)
}
render(){
let { name } = this.state
return (
<div onClick = { this.sum.bind( this,1,2 ) }>
<B name={ name }></B>
</div>
)
}
}
class B extends Component{
render(){
let { name } = this.props
return (
<Fragment>
</Fragment>
)
}
}
反向代理
安装中间件:yarn add http-proxy-middleware
新建文件:src/setupProxy.js
引入中间件
const proxy = require('http-proxy-middleware')
module.exports = function (app) {
app.use(proxy('/secoo', {
target: 'https://las.secoo.com',
changeOrigin: true,
secure: false,
pathRewrite: {
"^/secoo": ""
}
}))
第二个反向代理
app.use( proxy( '/xxx', {
}))
}
路由
-
安装
3.x
yarn add react-router
4.x
yarn add react-router-dom
-
两种模式( HashRouter / BrowserRouter )
1.老版本的 HashRouter :通过 hash 存储不同状态的 history 信息,对应 createHashHistory ,通过检测 location.hash 值的变化,使用 location.replace 方法实现 url 跳转。通过注册监听 window对象上的 hashChange 事件来监听路由变化,实现历史记录的回退
2.高版本浏览器的 BrowserRouter:利用 HTML5 里的 history 对应 createBrowerHistory,使用包括 pushState,replaceState 方法进行跳转。通过注册 window 对象上的 popstate 事件来监听路由变化,实现回退
需要后端支持 -
实现:
<Route path=" / " component={ Home }>
:路由展示区
exact
:路由完全匹配
路由在入口文件使用 Router
包裹路由组件
index.js
1.引入 HashRouter
import {HashRouter as Router} from 'react-router-dom'
2. 包裹需要路由的根组件
ReactDOM.render(
<Router> //包裹
<App />
</Router>, document.getElementById('root'));
App.js
import { NavLink,Route,Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<nav className="nav justify-content-center">
<NavLink activeclassName="active" className="nav-link active" to="/home">Home </NavLink >
<NavLink activeclassName="active" className="nav-link" to="/mine">Mine</NavLink >
<NavLink activeclassName="active" className="nav-link disabled" to="/list">List</NavLink >
</nav>
Route 就是路由展示区
<Route path="/home" component={Home}></Route>
<Route path="/mine" component={Mine}></Route>
<Route path="/list" component={List}></Route>
</div>
);
}
- a标签也可以,但是浏览器会显示一次跳转的状态,就是浏览器的刷新按钮在转圈圈
< Link to=" " >
标签可以避免 a 标签的问题。<NavLink>
用来路由激活的标签,除此之外与 Link 没什么区别,添加activeClassName
属性可以实现动态添加样式
重定向
重定向
exact 路由完全匹配,避免’/’ 被所有匹配到
1.引入 Redirect
import { Redirect,Route } from 'react-router-dom'
第一种:
<Route path="/" exact render={()=>(<Redirect to='home' />)} />
注意:必须加上 exact ,否则造成死循环
第二种:
<Route path="/" from='' to='' exact />
注意:使用 from='' to='' 需要在外边加上switch
第三种:(此方法取巧)
<Route path="/" component={ Home } exact />
路由激活:
imoprt { NavLink } from ‘react-router-dom’
<NavLink activeClassName = "active" className="" />
路由传参
路由组件的props上具备几个属性
history
push
replace
goBack
location
pathname
search
state
match
push会存进浏览器历史中 replace 不会存入历时记录中
传递:
to={{ pathName:'/mine/login',search:'?a=1&b=2', state:{count:1} }}
二级路由
二级路由与一级路由大致相同,在子路由中再添加路由展示区<Route path='' component={xx}>即可
路由监听
切换路由不能滚动问题
将APP变成路由组件
withRouter包裹 App组件
属性变化钩子
componentWillReactiveProps()
错误匹配
高阶组件
withRouter
问题:为什么Vue有导航守卫监听路由变化,react为什么没有
React-Hooks
可以让函数式组件使用 satae 、生命周期钩子、useContext、useRef,16.7版本以后出现的。
只能在顶层调用 Hooks 不要在循环,条件调用 hooks。
安装
npm i react-hooks -D
npx create-react-app react-hooks
useState
import React, {useState } from 'react'
const Hooks = () => {
const [count, setCount] = useState(0)
count是state的属性,useState()内是count的初始值,setCount是操作count的方法
return (
<div>
<button onClick={()=>setCount(count+1)}>修改count</button>
<p>count:{count}</p>
</div>
)
}
export default Hooks
uesEffect
类似componentDIdMount 或 componentDidUpdate
import React, { useState, useEffect } from 'react'
const Hooks = () => {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `你点击了${count}次`
})
return (
<div>
<button onClick={() => setCount(count + 1)}>修改count</button>
<p>count:{count}</p>
</div>
)
}
export default Hooks