React
- React介绍
- React官网初识
- React学习
- MVC
- MVVM
- JSX
- 外部的元素props和内部的状态state
- props
- state
- 生命周期
- constructor
- getDerivedStateFromProps
- render
- componentDidMount()
- shouldComponentUpdate
- getSnapshotBeforeUpdate(prevProps, prevState)
- 创建项目
- CRA:create-react-app
- create-react-app源码解析
- packages
- package/create-react-app
- index.js
- createReactApp.js
React介绍
React官网初识
react 官网
react github
团队中选型时候,如果没有历史包的话,就选择最新的react,如果有历史包的话,选择不影响历史包升级的版本
但是最新的也容易踩到坑
在React19中,主要看 新特性和带来的提升即可
新特性:
form类似于 react/field-form
这里的use可以理解为,添加响应式的逻辑
性能上的提升:
ref可以作为props,平常项目中使用的是improve API 去关联,而无法用props来关联
React学习
React 中小厂用的不多,但是大厂用的都是React
React:构建web和原生交互界面的库,React组件
本质上就是一个函数
- 像Vue一样支持
组件
的方式去拓展 - 支持原生元素标签,并且能够在标签中通过js的方法去执行
- 接收props,和state
…
与Vue相比较的话,React有一个典型的特性,React是单向数据流的
我们的视图
是由我们的交互
所决定的
ui视图 = render(data) 由数据所触发ui视图的变化
官网中,把这些内容看完就足够了
MVC
MVC model view controller
是一个架构的设计思路
controller:控制器 操纵数据导致视图变化
modal:数据
view:视图 视图中事件的触发也能够触发controller
// model
var myapp = {}; // 创建这个应用对象myapp.Model = function() {var val = 0;this.add = function(v) {if (val < 100) val += v;};this.sub = function(v) {if (val > 0) val -= v;};this.getVal = function() {return val;};/* 观察者模式 /var self = this, views = [];this.register = function(view) {views.push(view);};this.notify = function() {for(var i = 0; i < views.length; i++) {views[i].render(self);}};
};// view
myapp.View = function(controller) {var $num = $('#num'),$incBtn = $('#increase'),$decBtn = $('#decrease');this.render = function(model) {$num.text(model.getVal() + 'rmb');};/ 绑定事件 /$incBtn.click(controller.increase);$decBtn.click(controller.decrease);
};// controller
myapp.Controller = function() {var model = null,view = null;this.init = function() {/ 初始化Model和View /model = new myapp.Model();view = new myapp.View(this);/ View向Model注册,当Model更新就会去通知View啦 /model.register(view);model.notify();};/ 让Model更新数值并通知View更新视图 */this.increase = function() {model.add(1);model.notify();};this.decrease = function() {model.sub(1);model.notify();};
};// init
(function() {var controller = new myapp.Controller();controller.init();
})();
MVVM
MVVM: model viewModel view 数据的双向绑定
// model
var data = {val: 0
};// view
<div id="myapp"><div><span>{{ val }}rmb</span></div><div><button v-on:click="sub(1)">-</button><button v-on:click="add(1)">+</button></div>
</div>// controller
new Vue({el: '#myapp',data: data,methods: {add(v) {if(this.val < 100) {this.val += v;}},sub(v) {if(this.val > 0) {this.val -= v;}}}
});// Vue是不是MVVM?React呢?
// 严格来讲都不是。
// Vue操作的是虚拟的dom,而不是真实的dom节点,通过ref.current获取的结果,从虚拟上能够直接指向真实dom
// React:ui = render (data) 单向数据流
// Vue: ref 直接操作DOM,跳过了ViewModel
JSX
function VideoList({ videos, emptyHeading }) {const count = videos.length;let heading = emptyHeading;if (count > 0) {const noun = count > 1 ? 'Videos' : 'Video';heading = count + ' ' + noun;}return (<section><h2>{heading}</h2>{videos.map(video =><Video key={video.id} video={video} />)}</section>);
}
模板化的语言写法,需要注意的是,怎样将模板化的语言写法,转化为对象,最终转化为我们真实的dom
Vue和React都是通过AST去转化的,将我们这样的模板语言转化为js对象的能力:通过 babel
babel 是 js的compiler
compiler是代码执行之前,需要将代码从一种格式转换为另一种格式,方便后续代码的识别和执行
runtime是jsx代码中最终去消费babel编译后的产物,交由给_jsxs去做运行时的逻辑
babel
babel 本质:core-js,定义的js的各种各样的ecma标准都能做到
babel:将jsx模板写法转化为js对象的能力
jsx本质:借助于Babel的 @babel/preset-react 提供的包的能力转换出来的js对象,在compiler代码编译过程中做到的
绑定事件:
这个事件称之为,合成事件,并不是直接绑定到元素上的,通过驼峰的方式,将方法绑定到属性上
写业务开发的市场饱和了,要求是写业务代码基础上,能够将写业务代码的工具能掌握好
外部的元素props和内部的状态state
类似于函数
function bar(a,b){const d=1
}
a,b就像是props,而d就像是state
function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;}
}
props和state是怎么影响视图逻辑的?
组件的本质要么是类,要么是函数
props
export default function Profile(){return (<Avatarperson={{name:'Lin Lanying',imageId:'1dx5qh6'}}size={100}/>)
}interface IAvatarProps(){person?: Record<string,any>;size?: number|string;
}function Avatar(props: IAvatarProps){const {person,size=10}=props;
}
也可以这样作解构:
function Avatar({person,size}){const {person,size=10}=props;
}
state
useXXX => hooks的标志
import { sculptureList } from './data.js';
import { useState } from 'react';export default function Gallery() {const [index,setIndex]=useState(0) //hooksconst [showMore,setShowMore]=useState(false) //hooksfunction handleClick() {setIndex(index+1)}function handleMoreClick() {setShowMore(!showMore)}let sculpture = sculptureList[index];return (<><button onClick={handleClick}>Next</button><h2><i>{sculpture.name} </i> by {sculpture.artist}</h2><h3> ({index + 1} of {sculptureList.length})</h3><button onClick={handleMoreClick}>{showMore?'Hide':'Show'} details </button>{ showMore && <p>{sculpture.description}</p>}<img src={sculpture.url} alt={sculpture.alt}/> </>);
}
<></> => react 属性中得 Fragment 不表示任何意义,只是提供了一个容器。因为react只能接收一个根节点,因此能使用Fragment将根节点聚合起来
生命周期
- constructor:通过类引入constructor
- getDerivedStateFromProps:通过props的不同来找到state的区别
- render:实际渲染的动作
- Render阶段:react是虚拟dom,这个阶段是在内存中去执行的,并没有真正渲染
- Commit阶段:创建真实dom
- shouldComponentUpdate:判断组件是否需要更新,减少组件没有必要的重复渲染
- getSnapshotBeforeUpdate:上一次更新前的内容,提供了上一次的props和state
- componentDidUpdate:执行更新完之后的生命周期
- componentWillUnmount:组件卸载之前
constructor
class Counter extends Component {constructor(props) {super(props);this.state = { counter: 0 };this.handleClick = this.handleClick.bind(this);}handleClick() {// ...}
getDerivedStateFromProps
class Form extends Component {state = {email: this.props.defaultEmail,prevUserID: this.props.userID};static getDerivedStateFromProps(props, state) {// 每当当前用户发生变化时,// 重置与该用户关联的任何 state 部分。// 在这个简单的示例中,只是以 email 为例。if (props.userID !== state.prevUserID) {return {prevUserID: props.userID,email: props.defaultEmail};}return null;}// ...
}
render
import { Component } from 'react';class Greeting extends Component {render() {return <h1>Hello, {this.props.name}!</h1>;}
}
componentDidMount()
class ChatRoom extends Component {state = {serverUrl: 'https://localhost:1234'};componentDidMount() {this.setupConnection();}componentDidUpdate(prevProps, prevState) {if (this.props.roomId !== prevProps.roomId ||this.state.serverUrl !== prevState.serverUrl) {this.destroyConnection();this.setupConnection();}}componentWillUnmount() {this.destroyConnection();}// ...
}
shouldComponentUpdate
class Rectangle extends Component {state = {isHovered: false};shouldComponentUpdate(nextProps, nextState) {if (nextProps.position.x === this.props.position.x &&nextProps.position.y === this.props.position.y &&nextProps.size.width === this.props.size.width &&nextProps.size.height === this.props.size.height &&nextState.isHovered === this.state.isHovered) {// 没有任何改变,因此不需要重新渲染return false;}return true;}// ...
}
getSnapshotBeforeUpdate(prevProps, prevState)
不怎么经常使用的API,应用场景少
class ScrollingList extends React.Component {constructor(props) {super(props);this.listRef = React.createRef();}getSnapshotBeforeUpdate(prevProps, prevState) {// 我们是否要向列表中添加新内容?// 捕获滚动的位置,以便我们稍后可以调整滚动。if (prevProps.list.length < this.props.list.length) {const list = this.listRef.current;return list.scrollHeight - list.scrollTop;}return null;}componentDidUpdate(prevProps, prevState, snapshot) {// 如果我们有快照值,那么说明我们刚刚添加了新内容。// 调整滚动,使得这些新内容不会将旧内容推出视野。//(这里的 snapshot 是 getSnapshotBeforeUpdate 返回的值)if (snapshot !== null) {const list = this.listRef.current;list.scrollTop = list.scrollHeight - snapshot;}}render() {return (<div ref={this.listRef}>{/* ...contents... */}</div>);}
}
创建项目
pnpx create-react-app my-app
package.json:
这里的scripts里的指令本质:如何搭建一个自定义的webpack配置
CRA是脚手架,react-scripts是用CRA写的,CRA是用webpack写的
CRA:create-react-app
create-react-app
安装react,react-dom,react-scripts,基于cra-template模板安装的
创建git-commit
提供方法,react-scripts
create-react-app源码解析
create-react-app源码
- docusaurus 静态站点,包含了这些的说明
- website 静态站点
- docs 静态站点的说明
- .github
- PULL_REQUEST_TEMPLATE.md
- workflows 工作流,包含构建,通过github的钩子做的一些事情
- packages 多包结构
- lerna.json 基于lerna的多包结构
{"lerna": "2.6.0","npmClient": "yarn","useWorkspaces": true, //使用多包管理"version": "independent", //每个包的版本号不需要一致"changelog": { //更新添加元素"repo": "facebook/create-react-app","labels": {"tag: new feature": ":rocket: New Feature","tag: breaking change": ":boom: Breaking Change","tag: bug fix": ":bug: Bug Fix","tag: enhancement": ":nail_care: Enhancement","tag: documentation": ":memo: Documentation","tag: internal": ":house: Internal","tag: underlying tools": ":hammer: Underlying Tools"},"cacheDir": ".changelog"} }
- tasks 测试用例 通过shell脚本去写的 和test结合去用的,怎样基于测试用例执行事件
- test
- .alexrc rc,runtime config 运行时的配置
…
packages
- cra-template 模板
- create-react-app 由这个执行
pnpx create-react-app 命令
执行的是 create-react-app的package.json中的bin的指令,node作为二进制执行的入口,这个命令等效于在create-react-app目录下执行 node ./index.js
命令
package/create-react-app
index.js
'use strict';const currentNodeVersion = process.versions.node; //20.12.2
const semver = currentNodeVersion.split('.'); //通过.分割node版本号
const major = semver[0]; //获取第一位数字,即主版本号,如20if (major < 14) { //主版本号小于14的话就退出console.error('You are running Node ' +currentNodeVersion +'.\n' +'Create React App requires Node 14 or higher. \n' +'Please update your version of Node.');process.exit(1);
}
//执行CRA的init方法
const { init } = require('./createReactApp');init();
createReactApp.js
主要内容:
- commander
pnpx create-react-app -h
罗列指令及功能
pnpx create-react-app --info
输出环境信息
- init方法
function init() {const program = new commander.Command(packageJson.name).version(packageJson.version).arguments('<project-directory>').usage(`${chalk.green('<project-directory>')} [options]`).action(name => {projectName = name; //自己定义的项目名字,这里就是my-app}).option('--verbose', 'print additional logs').option('--info', 'print environment debug info').option('--scripts-version <alternative-package>','use a non-standard version of react-scripts').option('--template <path-to-template>','specify a template for the created project').option('--use-pnp').allowUnknownOption().on('--help', () => {console.log(` Only ${chalk.green('<project-directory>')} is required.`);console.log();console.log(` A custom ${chalk.cyan('--scripts-version')} can be one of:`);console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);console.log(` - a specific npm tag: ${chalk.green('@next')}`);console.log(` - a custom fork published on npm: ${chalk.green('my-react-scripts')}`);console.log(` - a local path relative to the current working directory: ${chalk.green('file:../my-react-scripts')}`);console.log(` - a .tgz archive: ${chalk.green('https://mysite.com/my-react-scripts-0.8.2.tgz')}`);console.log(` - a .tar.gz archive: ${chalk.green('https://mysite.com/my-react-scripts-0.8.2.tar.gz')}`);console.log(` It is not needed unless you specifically want to use a fork.`);console.log();console.log(` A custom ${chalk.cyan('--template')} can be one of:`);console.log(` - a custom template published on npm: ${chalk.green('cra-template-typescript')}`);console.log(` - a local path relative to the current working directory: ${chalk.green('file:../my-custom-template')}`);console.log(` - a .tgz archive: ${chalk.green('https://mysite.com/my-custom-template-0.8.2.tgz')}`);console.log(` - a .tar.gz archive: ${chalk.green('https://mysite.com/my-custom-template-0.8.2.tar.gz')}`);console.log();console.log(` If you have any problems, do not hesitate to file an issue:`);console.log(` ${chalk.cyan('https://github.com/facebook/create-react-app/issues/new')}`);console.log();}).parse(process.argv);if (program.info) {console.log(chalk.bold('\nEnvironment Info:'));console.log(`\n current version of ${packageJson.name}: ${packageJson.version}`);console.log(` running from ${__dirname}`);return envinfo //是一个会将当前环境都输出的包.run({System: ['OS', 'CPU'],Binaries: ['Node', 'npm', 'Yarn'],Browsers: ['Chrome','Edge','Internet Explorer','Firefox','Safari',],npmPackages: ['react', 'react-dom', 'react-scripts'],npmGlobalPackages: ['create-react-app'],},{duplicates: true,showNotFound: true,}).then(console.log);}if (typeof projectName === 'undefined') {console.error('Please specify the project directory:');console.log(` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`);console.log();console.log('For example:');console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);console.log();console.log(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);process.exit(1);}// We first check the registry directly via the API, and if that fails, we try// the slower `npm view [package] version` command.//// This is important for users in environments where direct access to npm is// blocked by a firewall, and packages are provided exclusively via a private// registry.checkForLatestVersion() //检查最新的版本号.catch(() => {try {return execSync('npm view create-react-app version').toString().trim(); //执行这个命令可以得到最新的版本号} catch (e) {return null;}}).then(latest => {if (latest && semver.lt(packageJson.version, latest)) {//有最新版本且没有升级到最新的版本,提示要升级到最新的版本了console.log();console.error(chalk.yellow(`You are running \`create-react-app\` ${packageJson.version}, which is behind the latest release (${latest}).\n\n` +'We recommend always using the latest version of create-react-app if possible.'));console.log();console.log('The latest instructions for creating a new app can be found here:\n' +'https://create-react-app.dev/docs/getting-started/');console.log();} else {const useYarn = isUsingYarn(); //判断是否使用yarncreateApp(projectName,program.verbose,program.scriptsVersion,program.template,useYarn,program.usePnp);}});
}
- checkForLatestVersion 获取最新的版本号
function checkForLatestVersion() {return new Promise((resolve, reject) => {https //通过http发送get请求读取版本号.get('https://registry.npmjs.org/-/package/create-react-app/dist-tags',//版本号res => {if (res.statusCode === 200) {let body = '';res.on('data', data => (body += data));res.on('end', () => {resolve(JSON.parse(body).latest);});} else {reject();}}).on('error', () => {reject();});});
}
- isUsingYarn():判断是否使用yarn
function isUsingYarn() {return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0; //使用环境变量判断是否使用yarn
}
- createApp()
function createApp(name, verbose, version, template, useYarn, usePnp) {const unsupportedNodeVersion = !semver.satisfies(// Coerce strings with metadata (i.e. `15.0.0-nightly`).semver.coerce(process.version),'>=14');if (unsupportedNodeVersion) {console.log(chalk.yellow(`You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +`Please update to Node 14 or higher for a better, fully supported experience.\n`));// Fall back to latest supported react-scripts on Node 4version = 'react-scripts@0.9.x';}const root = path.resolve(name);const appName = path.basename(root);checkAppName(appName);fs.ensureDirSync(name);if (!isSafeToCreateProjectIn(root, name)) {process.exit(1);}console.log();console.log(`Creating a new React app in ${chalk.green(root)}.`);console.log();// 首先创建一个最基础版const packageJson = {name: appName,version: '0.1.0',private: true,};// 写入fs.writeFileSync(path.join(root, 'package.json'),JSON.stringify(packageJson, null, 2) + os.EOL);const originalDirectory = process.cwd();process.chdir(root);if (!useYarn && !checkThatNpmCanReadCwd()) {process.exit(1);}if (!useYarn) {const npmInfo = checkNpmVersion();if (!npmInfo.hasMinNpm) {if (npmInfo.npmVersion) {console.log(chalk.yellow(`You are using npm ${npmInfo.npmVersion} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +`Please update to npm 6 or higher for a better, fully supported experience.\n`));}// Fall back to latest supported react-scripts for npm 3version = 'react-scripts@0.9.x';}} else if (usePnp) {const yarnInfo = checkYarnVersion();if (yarnInfo.yarnVersion) {if (!yarnInfo.hasMinYarnPnp) {console.log(chalk.yellow(`You are using Yarn ${yarnInfo.yarnVersion} together with the --use-pnp flag, but Plug'n'Play is only supported starting from the 1.12 release.\n\n` +`Please update to Yarn 1.12 or higher for a better, fully supported experience.\n`));// 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)usePnp = false;}if (!yarnInfo.hasMaxYarnPnp) {console.log(chalk.yellow('The --use-pnp flag is no longer necessary with yarn 2 and will be deprecated and removed in a future release.\n'));// 2 supports PnP by default and breaks when trying to use the flagusePnp = false;}}}run(root,appName,version,verbose,originalDirectory,template,useYarn,usePnp);
}
- setCaretRangeForRuntimeDeps()
function setCaretRangeForRuntimeDeps(packageName) {const packagePath = path.join(process.cwd(), 'package.json');const packageJson = require(packagePath);if (typeof packageJson.dependencies === 'undefined') {console.error(chalk.red('Missing dependencies in package.json'));process.exit(1);}const packageVersion = packageJson.dependencies[packageName];if (typeof packageVersion === 'undefined') {console.error(chalk.red(`Unable to find ${packageName} in package.json`));process.exit(1);}// 手动注入react,react-dommakeCaretRange(packageJson.dependencies, 'react');makeCaretRange(packageJson.dependencies, 'react-dom');fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);
}
手动注入react,react-dom,然后将 cra-template 模板拼上去,就生成了cra代码
- 后面就是判断依赖,告诉有哪些异常,异常的话就过滤删除
CRA主要做的事:读取用户的输入,根据用户输入创建出对应的模板,写入到指定的目录下