React与传统MVC的关系

react的特性

- 声明式:我只告诉你我想的东西是什么,具体怎么实现不需要我去实现;只需要根据react模板写好的规则去书写即可,而react怎么把数据映射到视图上的、怎么渲染到页面上的不需要我们去关心;将来数据发生了改变,react怎么去更新模板不需要我们去关心,我只负责改变数据即可;我们只关注数据层的改变
- DOM操作频繁十分影响前端性质,虚拟DOM可以减少DOM的交互
- JSX扩展文件名:将HTMLCSSJS合并在一个组件中,然后react处理器解析JSX文件
- 单向数据流:所有的掌控权都在顶层:顶层可以直接传数据给底层,而底层想要改变什么需要请示顶层授权;数据从父组件流向子组件,如果数据改变,需要从子组件传向父组件,父组件更新完毕后再次流向子组件
虚拟DOM
- 传统DOM

如果减少一个DOM,底下的DOM需要往上移,必须经历文档重排回流的过程,浏览器负荷大,性能差 - 虚拟DOM

虚拟DOM其实就是用JS对象把数据显示出来,然后比较前后两个JS对象的DOM树节点
比如说,原始数据为[11, 22, 33]
虚拟DOM就是将其转为JS对象{type: 'li', text: 11} {type: 'li', text: 22} {type: 'li', text: 33}
更新后的虚拟DOM为{type: 'li', text: 11} {type: 'li', text: 33},即删除掉了第二条数据
然后两个虚拟DOM对比(diff算法,最小代价进行对比),第一条数据与第二条数据节点都没有改变,第二条数据节点不同,仅把第二条数据节点转换为补丁,更新到真正的DOM中即可
也可称为virtual dom vdom vnode
脚手架create-react-app


react项目目录结构

编写第一个react应用程序

src文件夹下面新建index.js文件,为项目的入口文件(这是react脚手架本身就配置好的),里面的代码自动跑;
同理public文件夹下的index.html为入口html文件,其中最主要的就是挂载的id为root的div

import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(<h1>你好react</h1>, document.getElementById("root"));
感觉将html与js混合写在js文件里面
这就是JSX语法(js+xml)
JSX语法

源码如下:
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(<div>
你好react
</div>, document.getElementById("root"));
下面这一段代码使用React.createElement,与上面这段代码的效果一致:
ReactDom.render(React.createElement("div", {
id: 'id',
className: 'class'
}, '111111'), document.getElementById('root'))

Babel的JSX编译器,实际上就是帮我们把第一段代码(类似HTML的JSX结构)编译成第二段代码(js的对象结构),只有这样浏览器才能认识这段js语句

组件的创建——类组件

首先先复习es6的类如何使用
// 类用大写字母
class Test {
// 构造器函数
constructor() {
// 成员属性
this.a = 1
}
// 成员函数
testA() {
console.log('testA')
}
}
// 继承类
class ChildTest extends Test {
testB() {
console.log('testB')
}
}
var obj = new Test()
obj.testA()
console.log(obj.a)
var childObj = new ChildTest()
childObj.testA()
console.log(childObj.a)
然后在index.js里面import这个js文件即可
import './components/classComp';
然后开始写类组件,可以复用
import React from 'react';
// 继承react的组件类
class App extends React.Component {
render() {
return (
<div>HELLO REACT COMPONENT</div>
)
}
}
export default App
在index.js中引入App后,把它当标签用即可
import React from 'react';
import ReactDom from 'react-dom';
import App from './components/classComp';
ReactDom.render(<App></App>, document.getElementById('root'))

快捷键:输入rcc加回车,自动生成class组件
import React, {
Component } from 'react'
export default class nestComp extends Component {
render() {
return (
<div>
</div>
)
}
}
组件的创建——函数组件
拓展:JSX必须有一个父元素包裹住里面的内容

function App () {
return <div>functional component</div>
}
export default App
下段代码与上面代码效果一模一样,注意:如果return后面想要换行,那么必须包上括号
function App () {
return (
<div>functional component</div>
)
}
export default App
在react版本16.8之前,函数式组件是没有状态state的,因此使用的比较少;16.8版之后,函数式组件也引入了react hooks
组件的创建——嵌套

父子组件,兄弟组件
以下抽取组件,注意三个子组件的三种不同写法,都可以
import React, {
Component } from 'react'
class Navbar extends Component {
render() {
return (
<div>navbar</div>
)
}
}
function Swiper () {
return (
<div>swiper</div>
)
}
const Tabbar = () => <div>tabbar</div>
export default class nestComp extends Component {
render() {
return (
<div>
<Navbar></Navbar>
<Swiper></Swiper>
<Tabbar></Tabbar>
</div>
)
}
}
注意,此时要想在Navbar里面再嵌套一个子组件,应该如下嵌套:
class Navbar extends Component {
render() {
return (
<div>
navbar
<Child></Child>
</div>
)
}
}
组件的样式(行内样式)
react中的一对大括号为模板语法规则,里面的内容会按照js指令执行
import React, {
Component } from 'react'
export default class styleComp extends Component {
render() {
let myName = 'corn6'
return (
<div>
{
10 + 20}-{
myName}
{
10 > 20 ? 'aaa' : 'bbb'}
</div>
)
}
}

注意,react中组件的行内样式style,只能为对象形式,如下:
import React, {
Component } from 'react'
export default class styleComp extends Component {
render() {
let obj = {
background: 'yellow'
}
return (
<div>
{
/* 此处obj外面包裹一个大括号,是因为要解析为变量 */}
<div style={
obj}>111</div>
</div>
)
}
}
因此看到下面这段代码,不要理解为双大括号;里面一层括号是因为react中组件的行内样式style,只能为对象形式;而外面一层括号就是react的模板,用于解析js变量
<div style={
{
background: 'red'}}>22222</div>
let obj = {
// js中的变量,不出现连字符-,改为驼峰写法
backgroundColor: 'yellow',
fontSize: '30px'
}

官方推荐的就是行内样式!!
组件的样式(class)

用className声明好类名
<div className='active'>33333</div>
<div id='myapp'>44444</div>
新建一个css文件,写样式
.active {
background: blue;
}
#myapp {
background: green;
}
回到组件的js文件,import css文件
import '../css/styleComp.css'
之所以可以在js文件里面直接引入css文件,是因为背后有脚手架封装的webpack的支持
拓展,编译过后,实际上就是把css作为内部样式插入到head标签里面了,这是webpack帮我们做的,如下图

for关键字改为htmlFor
跟上述class改为className一样的道理,因为它们原先都是js中的关键字,没写清楚的话浏览器还需要费时间去解析
<label htmlFor='username'>用户名:</label>
<input type='text' id='username'></input>
事件处理4形式
import React, {
Component } from 'react'
export default class methodComp extends Component {
render() {
return (
<div>
<input></input>
<button onClick={
() => {
console.log('click')
}
}>Add</button>
<button onClick={
this.handleClick }>Add2</button>
<button onClick={
this.handleClick3 }>Add3</button>
<button onClick={
() => {
this.handleClick()
this.handleClick3()
}
}>Add4</button>
</div>
)
}
handleClick() {
console.log("click2")
}
handleClick3 = () => {
console.log("click3")
}
}
注意onClick后面的事件需要用一个大括号包裹,并且推荐使用箭头函数写,没有this的问题
注意第二三种,调用时不要加()
注意第四种,调用时要加(),并且在逻辑很复杂的情况下,使用第四种调用方法可维护性更高;并且后续需要传入参数的时候,只能使用第四种!
各种形式事件处理this指向问题
首先讲清楚js改变this指向的3种方法
/*
js改变this指向的3种方法:
call,
apply,
bind
*/
var obj1 = {
name: 'obj1',
// this => 谁调用我指向谁
// 此时this指向obj1
getName() {
console.log(this.name)
}
}
var obj2 = {
name: 'obj2',
getName() {
console.log(this.name)
}
}
obj1.getName()
// call用于修正this指向,
// 首先找到getName方法,然后强行让getName方法的this改为指向obj2
// call:改变this指向,同时自动函数
obj1.getName.call(obj2)
// apply同call:改变this指向,同时自动函数
obj1.getName.apply(obj2)
// bind:仅改变this指向,要想执行函数,手动添加()
obj1.getName.bind(obj2)()

然后声明一个状态state,观察各种形式如何获取到这个state
import React, {
Component } from 'react'
export default class methodComp extends Component {
a = 100
render() {
return (
<div>
<input></input>
{
/* 第一种,函数里面的this,跟外面render()的this保持一致 */}
<button onClick={
() => {
console.log('click1', this.a)
}
}>Add</button>
{
/* this指向谁:谁调用我我就指向谁
第二种,handleClick被react调用,this指向undefined
总之,不会指向App实例
因此,想要在这种调用方法里面访问到App的state,需要改变this的指向
使用.bind(this),让handleClick方法里面的this,指向外面的这个this.handleClick的this即可
*/}
<button onClick={
this.handleClick.bind(this) }>Add2</button>
{
/*
第三四种,又是箭头函数
根本不关心谁调用了我,永远保持跟外部作用域相同
*/}
<button onClick={
this.handleClick3 }>Add3</button>
<button onClick={
() => {
this.handleClick()
this.handleClick3()
}
}>Add4</button>
</div>
)
}
handleClick() {
console.log("click2", this.a)
}
handleClick3 = () => {
console.log("click3", this.a)
}
}
因此不推荐第二种写法,其他三种都可以
总结如下:

react事件绑定与原生事件绑定的区别
react并不会真正地绑定事件到每一个具体的元素上,而是使用事件代理的模式:将事件绑定在根节点上,然后模拟一套冒泡机制实现,好处是占用内存很小,因为不需要将事件绑定到具体某个元素上
event事件对象

// 事件对象evt不是我们传的,而是react内部构建好的
handleClick3 = (evt) => {
console.log("click3", this.a, evt)
}
打印出来如下:

同样有阻止冒泡,默认行为evt.target
ref的应用

import React, {
Component } from 'react'
export default class methodComp extends Component {
a = 100
render() {
return (
<div>
{
/* ref,起引用的作用
后续想要拿到input,可以直接用this.refs.mytext拿到整个input标签
*/}
<input ref='mytext'></input>
<button onClick={
() => {
console.log('click1', this.refs.mytext, this.refs.mytext.value)
}
}>Add</button>
</div>
)
}
}

然而以上直接给ref一个字符串值的写法不被提倡,以下写法更为标准:
调用React.createRef方法赋给一个状态变量,再把这个变量绑到ref上,由于变量无法重复声明,因此比字符串方法更安全
import React, {
Component } from 'react'
export default class methodComp extends Component {

本文详细介绍了React的基础知识,包括React与传统MVC的区别、虚拟DOM的概念和优势、创建React应用的脚手架create-react-app以及项目目录结构。重点讲解了JSX语法、组件的创建(类组件、函数组件和嵌套组件)、样式处理、事件处理、状态管理和数据流。此外,还通过多个实战案例,如todoList和影院查询,深入探讨了React的使用技巧和最佳实践。
最低0.47元/天 解锁文章
895

被折叠的 条评论
为什么被折叠?



