React 基础与源码分析

.gitignore:这里面写的文件不会被提交到 git 仓库上去,因为它的体积太大了,例如node_modules。
因为 react 也是单页面应用,所以 pubulic 文件夹下有 index.html 作为入口文件:

<div id="root"></div>

但是项目运行的入口文件是 src 下的 index.js 文件,里面有:reactDOM.render(路由组件,document.getElementById('root'))
库和框架的区别:
库:小而精,只是提供了特定功能的api,优点是船小好调头,很方便的从一个库切换到 另一个库,代码基本不会变
框架:大而全,框架提供了一整套的解决方案,所以在一个项目中想从一个框架换到另一个框架是比较困难的。

原生 js、react 和 vue:

原生js:1、频繁操作dom,频繁引起浏览器的重绘、重排,效率低;
2、js有模块化,例如有个a.js文件使用很频繁,我们可以将它抽离出来各个页面想用就直接import引入就用(node.js),但是它没有组件化的编码方案,代码复用率低,组件将一个页面的每一个功能点都能拆分成一个个小页面,大页面需要直接引入即可,很灵活方便也降低了页面数据的耦合。
好处:随着页面规模的增大,模块和组件也就越来也多,到了中后期开发新页面的时候可能就不需要写那么多的代码,很多代码都是直接把组件拿过来用即可。
vue:最火的框架,因为他由中国人开发的一款框架,关注他的人比较多,文档对国人更友好。
react伟大之处:react 是 faceBook 团队开发的,2013 就开发出来了,15 年就开源了,团队强大,设计优秀,发展到现在是很成熟的了,react 把页面分成一个个独立的组件,这些组件可以自由组合嵌套组成我们的页面,他最伟大的地方就是发明了组件,真正的把组件落到实处了,我们可以在浏览器上写一个 myComponent 这样一个标签了,去渲染我们要的这样一个功能。不像 vue 一样是个单文件组件,他就只是一个 jsx,所有东西都可以放在里面,减低我们的开发成本、管理成本。
react高性能的体现:虚拟dom,不跟真实dom做交互,用js去构建dom树,不放在页面上,他放在计算机的内存里,让用户体验更好
单向数据流:数据是从一个组件流向另一个组件,不能在组件内部修改组件外部的数据。
为什么使用jsx而不是js?:js 繁琐,jsx 简单有层次感,但是解析时通过 babel 会解析成 js 的 React.creatElement(‘h1’, {id: ‘title’},‘hello world’)的格式。

react 与 vue 两大框架的对比

vue是典型的mvvm框架;而react本身是组件化框架
mvvm好处:关注 model 的变化,让框架自动去更新 DOM 的变化,把开发者从繁琐的DOM 操作中解放出来
mvvm的缺点
1、bug 较难被调式,因为使用了双向绑定模式,当页面异常时,可能是 view 的代码出了问题,或是 model 的代码出了问题,bug 不好定位,另外,数据绑定的声明是 view 模板中的,没法 debugger 调试
2、一个大的模块中 model 也会很大,虽然 mvvm 使用方便也容易保证数据的一致性,但是长时间的持有不释放内存,就会造成花费更多的内存
3、在大型图型应用程序中,视图状态较多,viewModel 的构建和维护成本都会变高
构建思想不同
vue 的思想是响应式的,每一个属性都要创建一个 watcher 来监听,当属性变化时,响应式的更新对应的虚拟 DOM
react 是自上而下的单向数据流,包含容器组件和展示组件,容器组件 负责处理复杂的业务逻辑而展示组件负责UI层
模板上的不同
vue 使用模板,例如 v-if、v-show、v-for 等等
react 使用 JSX,模板和 js 混在一起,<中写模板>,{中写 JS}
模板语法上 react 的 JSX 更甚一筹、而模板分离上,个人更倾向于 vue

虚拟DOM由来

其 vdom 是基于 snabbdom 库所做的修改。snabbdom 是一个开源的 vdom 库。snabbdom 的主要作用就是将 JS 模拟的 DOM 结构转换成虚拟的 DOM 节点。
h函数:将 JS 模拟的 DOM 结构转换成虚拟 DOM
patch函数 :将虚拟 DOM 转换成真实的 DOM 渲染到页面中。
为了保证页面的最小化渲染,snabbdom 引入了 Diff 算法 ,通过 Diff 算法找出前后两个虚拟 DOM 之间的差异,只更新改变了的 DOM 节点,而不重新渲染未改变的 DOM 节点。

创建虚拟 DOM

js 写虚拟 dom:
// React.creatElement(标签名,属性,内容) 和原生 document.createElement(标签名,属性,内容) 很相似,不同的是前者创造一个虚拟dom而后者创造一个真实dom
React.creatElement('h1', {
    
    id: 'title'}'hello world') // <h1 id="title"> hello world </h1>
jsx写虚拟dom:
const domV = <h1 id="title"> hello world </h1>

虚拟 dom

Dom 本质:浏览器端的概念,用 js 对象来表示页面中的元素,例如页面中有一个 div,你能通过 document.getElementById 拿到这个 div,也就是拿到一个 dom 对象,他身上的属性较多,较”重“,也提供了操作 dom 的 api,例如通过 .style 改变拿到的 div 的样式等等。
虚拟dom:框架提供的,通过 js 对象的形式来模拟页面上 DOM 和 BOM 的嵌套关系,react 内部在用,身上属性少,较”轻“,目的是为了实现页面上 dom 元素高效更新,做到按需渲染组件,只渲染更新的数据所对应的页面元素,例如我们改变了表格里的前两行数据,传统的 dom 会导致这个表格重新渲染,但是虚拟 dom 只会更新变换的那两行,其它不变的不渲染。它是通过对比新旧两棵 dom 树,得到需要更新的 dom 元素,实施更新,但是浏览器中并没有提供获取 dom 树的 api,所以我们无法拿到内存中的 dom 树,但是我们可以用 js 对象的形式来手动模拟浏览器中的 dom 树的嵌套关系,就是框架中的虚拟 dom 树。
例如:

<div id="myDiv" title="我是title" dataIndex="我是自定义属性" >
我是XXX<p>不信你来看</p></div>

用 js 来构造虚拟 dom 树:

let div = {
    
    
	tagName:"div",
	attrs:{
    
    
		id:myDiv,
		title:'我是 title'dataIndex:'我是自定义属性'
	},
	children:[
		'我是 XXX',
		tagName:"p",
		attrs:{
    
    },
		children:[
			'不信你来看'
		]
	]
}

虚拟 dom 树的概念:
本质:用 js 对象来模拟真实 dom 元素的嵌套关系。
目的:为了实现页面元素的高效更新。

真正的 react 中创建虚拟 dom 元素:

const myp = React.creatElement('p',null,'不信你来看')
const mydiv = React.creatElement('div',{
    
    id:'myDiv',title:'我是title',dataIndex:'我是自定义属性'},'我是XXX',myp)  // 标签嵌套,把上一个标签放到第四个参数位置即可
// 最后使用React.render(mydiv,document.getElementById('app'))把虚拟dom渲染到页面上.

但是开发过程中写 react.creatElement 的形式写 html 比较麻烦,可读性较差,所以 react 里出现了 JSX 的语法,但是我们需要用 babel 来转换 jsx,在运行的时候本质上还是以 react.creatElement 的形式来运行的。

原生JS操作DOM真的慢吗?

不慢
测试:向页面上插入 5 万个 div,js 用了 77 毫秒操作完成,但是浏览器渲染需要 1 秒才能完成,所以不是 js 慢,是浏览器渲染慢,浏览器未渲染完毕页面就不可交互。所以当需要操作的 DOM 达到一定的数量的时候,react 反而比原生 JS 慢很多,所以当操作的 DOM 数量庞大的时候,我们要手动去优化它的性能,但 vue 却不存在这个问题。

虚拟 DOM 的优点或者说他为什么比真实 DOM 快

1、它可以减少 DOM 操作,如果他直接操作 DOM,那它肯定不可能比真实 DOM 快,因为他是基于操作 DOM 去操作 DOM,怎么可能比它快呢?如果我们要向页面上放 1000 个 div,在 vue 和 react 出现之前我们用 js 先添加第一个,再添加第二个,一个一个的添加,最后添加到 1000 个,就很慢;而 react、vue 能做到就往数组里放 1000 个文本,他一次性把它们放到页面上,1000 次操作就被优化成了 1 次操作,就变快了;
2、虚拟 DOM 借助 DOM diff 把多余的操作省掉,比如你添加了 1000 个节点,但只有 10 个节点是新增的,他就只添加这 10 个;
3、跨平台;因为虚拟 DOM 本质上是一个 js 对象,他不只可以变成 DOM,还可以变成小程序,IOS 应用,安卓应用等

虚拟 DOM 的缺点

我们需要额外的创建函数来创建 DOM,如 react 中的 createElement 或 vue 中 render 的 h,但可以用 JSX 来简化成 XML 写法,通过 bebal 转换。vue 中有 vue-loader 转义一下,能让我们正常写 .vue 文件。但是纵使这样也有缺点,它严重依赖打包工具,不打包的话,这语法 js 不认识,所以他们要添加额外的构建过程

DOM-diff 是干什么的?

将两棵新旧 DOM 树逐层对比,找出哪些节点需要更新
如果节点是组件,就先看类型,类型不同直接替换(删除旧的),如果类型相同就只更新属性,然后深入组件做递归。
如果节点是原生标签,则只看标签名,标签名不同直接替换,相同则只更新属性,然后进入标签后代做递归。

Diff 算法

diff 算法对比的最小力度是标签,但是标签里面套标签时,套在里面的标签也不会被重新渲染,因为 diff 算法不止对比一层,是逐层对比,递归查找。
react 和 vue 中的 key 有什么用?
答:当状态中的数据发生改变时,react 会根据新的数据生成新的虚拟 dom 树,随后 react 用新的虚拟 dom 树和旧的虚拟 dom 树进行 diff 比较(肯定是虚拟 dom 进行的比较,因为真实 dom 都放到页面中去了,还对比啥?)
对比规则如下
情况一、旧的虚拟 dom 中找到与新的虚拟 dom 中相同的 key,若虚拟 dom 中的内容没有改变,直接用之前的真实 dom。
情况二、旧的虚拟 dom 中找到与新的虚拟 dom 相同的 key,对比虚拟 dom 中的内容,若内容对比不一致,就替换调页面中的真实 dom
情况三、旧的虚拟 dom 中未找到新的虚拟 dom 中相同的key,直接根据新的虚拟 dom 创建新的真实 dom 放到页面上
用 index 作为 key 可能会引发问题:1、若对数据进行逆序添加,逆序删除等破坏顺序的操作,可能会发生没必要的 dom 更新,页面显示没问题,但是效率变低了;2、如果结构中包含输入类的 dom,产生了错误更新=>页面 input 框的显示会出现问题。若数据中有唯一标识,一定要用唯一的,不用 index

tree diff: 新旧两棵 dom 树,逐层对比。当整棵 dom 树逐层对比完毕,则一定能找到需要更新的元素。
component diff : 在进行 tree diff 的时候,每一层中各个组件之间的对比。
1、如果对比前后,组件的类型相同,则认为
暂时
不需要更新。
2、如果对比前后,组件类型不同,则我们需要移除旧组件,创建新组件,并追加到页面中。
element diff:在进行组件类型对比的时候,如果组件类型相同,则需要进行元素之间的对比。
在这里插入图片描述

类组件中的 createRef()

import React, {
    
     Component, createRef } from 'react';
class myForm extends Component {
    
    
// 这里我们直接写 onSearch、state 没有用 const、let 等定义,因为类中直接写赋值语句表示直接向类
//的实例身上添加属性或方法,但是写在 construct 构造函数中代表向原型对象上添加方法,而不是在实例
//对象上添加的,实例需要通过原型链找到对应的方法
	state = {
    
    }
	refForm = createRef()
	onSearch = ()=> {
    
    
		const {
    
     receiveDate, ...values } = this.refForm.current.getFieldsValue();
	}
	 // 当没有点击搜索有搜索内容后点击分页,需要重置
   pageChangeResetForm = (data) => {
    
    
       this.refForm.current.resetFields();
   	   this.refForm.current.setFieldsValue({
    
     ...data });
  }
  ...
  render(){
    
    
  	 return (<>
  			<Form onClick = {
    
     this.onSearch } ref={
    
    this.refForm} />
      	</>)
  }   	
}

为什么 React 中是 onClick 与 onBlur (失去焦点)等等,而不是原生 js 里的 onclick、onblur 等等?

1、react 中使用的是自定义事件,它将原生中的事件都重新封装了一遍,而不是直接使用原生js 的 dom 事件——为了更好的兼容性。
2、react 中的事件是通过事件委托的方式去处理的(委托给组件最外层的元素,原理:事件冒泡)——为了高效。
另外,我们可以通过 event.target 能获取发生事件源的 dom 对象,也就是不用写 ref 我们也能获取 input 框的 value(也就是 react 官网说的,不要过度使用 ref),用 event.target.value 即可。

react 中的受控组件与非受控组件

非受控组件:页面中输入类 DOM 的值,例如 input 框,checkbox,单选和多选都算,如果是现用现取,就是非受控组件,它不受 setState 的控制。例如 form 表单,点击登录时触发回调函数,在函数中直接取获取 input 的 Dom 节点,拿到 input 的 value 值,就属于现用现取。

import React, {
    
     createRef, Component } from 'react';
class Login extends Component{
    
    
	userNameRef = createRef()
	passwordRef = createRef()
	handleSubmit = (event)=>{
    
    
		event.preventDefault() // 阻止表单提交
		console.log(`你输入的用户名是${
      
      this.userNameRef.value},你输入的密码是${
      
      this.passwordRef.value}`)
	}
	
	return (
		<form onSubmit={
    
     this.handleSubmit }>
			<input ref=this.userName type="text" name="userName"/>
			<input ref=this.password type="password" name="password"/>
		</form>
	)
}

受控组件:页面中输入类 DOM 的值,例如 input 框,checkbox,单选和多选都算,随着我们的输入,就把值维护到状态里面(通过 onChange 实现),等需要用值的时候,直接从状态中去取出来用。就像 vue 里的双向数据绑定

import React, {
    
     Component } from 'react';
class Login extends Component {
    
    
	state = {
    
    
		password:'',
		username:''
	}

	handleSubmit = (event) => {
    
    
		event.preventDefault() // 阻止表单提交
		const {
    
     username, password } = this.state
		console.log(`你输入的用户名是${
      
      username},你输入的密码是${
      
      password}`)
	}
	
	saveUserName = (event) => {
    
    
		this.setState({
    
     username: event.target.value })// 随着每次输入把 input 的值存在状态里
	}
	
	savePassword = (event) => {
    
    
		this.setState({
    
     password: event.target.value })
	}
	
	render() {
    
    
		return (
			<>
				<form onSubmit = {
    
     this.handleSubmit }>
				{
    
    /* this.saveUserName 后面别加(),不然他就是将 saveUserName 函数的返回值 undefined,交给 onChange 作为回调,因为{}里的代码会被当做 js 被 react 执行 */}
					<input onChange = {
    
     this.saveUserName } type = "text" name = "username"></input>
					<input onChange = {
    
     this.savePassword } type = "password" name = "password"></input>
				</form>
			</>
		)
	}
}

受控组件与非受控组件的适用场景以及优缺点

建议还是多使用受控组件,因为非受控组件里面需要使用 ref 获取 dom,但是 react 官网明确表示,让我们别过度使用 ref,效率不高,而受控组件的优势就在于能够省略 ref。
她俩的运用场景:当我们不对用户的输入做实时操作,用非受控,反之,用受控
受控与非受控都行:一次性检索(例如表单提交)
需要用受控组件:及时验证、有条件的禁用提交按钮、一个数据的几个输入
受控组件的缺点:有时比较麻烦,我们需要为每个数据变化的每种方式都写上 onChange 事件,并通过一个react组件传递所有的 state,当业务变更的时候,可能需要修改很多个 onChange,就很烦了

高阶函数和函数柯里化

对象方面的知识:

let a = 'name'
let obj = {
    
    }
obj[a] = 'tom'
console.log(obj) // { name: tom }

高阶函数和函数柯里化概念:

// #region  当注释了一段代码后发现代码就折叠不起来了,用region能解决
/*
高阶函数:一个函数接收的值是一个函数或者函数的返回值是一个函数,满足其中一个条件的就是高阶函数,例如 setTimeout,arr.map 等等。
函数柯里化:函数调用后继续返回函数,实现多次接收参数,最后统一处理,得出结果的函数编码形式。
下面代码的 saveFormDate 就是柯里化函数,实现多个参数相加求和也能用柯里化实现,add(1)(2)(3),可以去看我的一篇函数柯里化博客。
*/
// #endregion
import React, {
    
     Component } from 'react';
class Login extends Component {
    
    
	state = {
    
    
		password:'',
		username:''
	}

	handleSubmit = (event) => {
    
    
		event.preventDefault() // a阻止表单提交
		const {
    
     username, password } = this.state
		console.log(`你输入的用户名是${
      
      username},你输入的密码是${
      
      password}`)
	}
	// 柯里化函数:
	saveFormDate = (datatype) => {
    
     // 返回一个函数,并且第一次接收datatype这个参数
		return (event) => {
    
     // 第二次接收event这个参数
			this.setState({
    
    [datatype] : event.target.value }) // 最后对函数进行了统一处理
		} 
	}
	
	render() {
    
    
		return (
			<>
				<form onSubmit = {
    
     this.handleSubmit }>
				{
    
    /* this.saveUserName后面别加(),不然他就是将saveUserName函数的返回值undefined,交给onChange作为回调,因为{}里的代码会被当做js被react执行,但是我们能通过调用后返回一个函数,加()实现传参,一个函数通过传参的不同执行不同的功能(函数柯里化) */}
					<input onChange = {
    
     this.saveFormDate(userName) } type = "text" name = "username"></input>
					<input onChange = {
    
     this.saveFormDate(password) } type = "password" name = "password"></input>
				</form>
			</>
		)
	}
}

不用函数柯里化:

import React, {
    
     Component } from 'react';
class Login extends Component {
    
    
	state = {
    
    
		password:'',
		username:''
	}

	handleSubmit = (event) => {
    
    
		event.preventDefault() // a阻止表单提交
		const {
    
     username, password } = this.state
		console.log(`你输入的用户名是${
      
      username},你输入的密码是${
      
      password}`)
	}
	// 不使用函数柯里化
	saveFormDate = (datatype) => {
    
     
			this.setState({
    
    [datatype] : event.target.value }) 
	}
	
	render() {
    
    
		return (
			<>
				<form onSubmit = {
    
     this.handleSubmit }>
					<input onChange = {
    
    (event) => this.saveFormDate(username, event)} type = "text" name = "username"></input>
					<input onChange = {
    
    (event) => this.saveFormDate(password, event)} type = "password" name = "password"></input>
				</form>
			</>
		)
	}
}

使用脚手架开发项目的特点:

模块化,组件化,工程化:在项目中使用了 webpack 全自动工具,你的写了一段代码,他能帮你进行语法检查、代码压缩、语法转换、兼容性处理等等一系列自动的东西。
脚手架中对 public 目录下的 index.html 文件的分析:

脚手架中对public目录下的index.html文件的分析
如果公共样式会引起冲突,就需要使用样式模块化
以前引入样式:import ‘./index.css’
样式模块化:将 index.css 文件名改为 index.module.css,引入时:import hello from ‘./index/module.css’,但这种用得不是很多,因为用less写css的时候,能嵌套着写,就能避免产生冲突。

React 源码分析:

从 0 实现 react
用 JSX 定义一个元素并把它渲染到页面上:

const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)

JSX 通过 Babel 等构建工具转换为 JS,现在我们用 JS 替换上面代码的第一行:

const element = React.createElement(
  "h1",
  {
    
     title: "foo" },
  "Hello"
)

这就是一个元素,一个具有两个属性的对象:type 和 props(它有更多的,但我们只关心这两个)。

该 type 是一个字符串,我们要创建的 DOM 节点的指定类型,它是 tagName 你传递给document.createElement 当你想创建一个 HTML 元素。
props 是另一个对象,它具有来自 JSX 属性的所有键和值,children 在这种情况下是一个字符串,但它通常是一个具有更多元素的数组。这就是为什么元素也是树的原因。

下面我们实现一个与 react 相同的应用程序,但是不使用 react

第一步

const element = {
    
    
  type: "h1",
  props: {
    
    
    title: "foo",
    children: "Hello",
  },
}const container = document.getElementById("root")const node = document.createElement(element.type)
node["title"] = element.props.title
​
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
​
node.appendChild(text)
container.appendChild(node)

用我们自己的版本替换 react 代码

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)

将 jsx 转化为 js

const element = React.createElement(
  "div",
  {
    
     id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
)

编写我们自己的 createElement 函数
我们在第一步中看到,元素其实是一个对象,带有 type 和 props,所以 createElement 函数唯一要做的就是创建那个对象。

function createElement(type,props,...children){
    
    
	return {
    
    
		//type:type
		type,
		props:{
    
    
			...props,
			children
		}
	}
}

持续更新中…

猜你喜欢

转载自blog.csdn.net/xiaoguoyangguang/article/details/117307550
今日推荐