1. 安装
-
- npm install -g webpack
-
- npx create-react-app my-app
-
- cd my-app
-
- npm start
-
- npm i antd
- css modules
- npm i --save-dev react-router-dom
注释:{/** **/}
2. 文档内容
2.1 在react中ReactDom相当于document,只不过是操作虚拟DOM,跟vue的区别:vue操作真实DOM,效率慢。但是React操作虚拟DOM
2.2
- ReactDOM.render(arg1, arg2)
- arg2: docukment.getElemntById("root") 在root下渲染
2.3. es5定义类
function A(name) {
this.name = name
}
const a = new A('lixin')
es6定义类
class P {
constructor(name)
}
const p = new P('lixin')
2.4 vscode添加插件
setting->include language -> javascript javascriptreact triiger-> 打勾使用tab键
2.5 空标签<></>
react中必须要有根元素,可以用空标签。无法用className等属性
2.6 htmlFor和id一起,有联动效果
<label htmlFor="name">name</label>
<input id="name" />
2.7 虚拟Dom为什么效率高于真实
虚拟Dom的携带属性数量小于真实DOm
2.8 循环 只可以用map, 要有key
return (
<>
{
arr.map(item, index) => {
<li key={index}>{item}</li>
})
}
</>
)
2.9 单纯修改变量无法触发render函数,所以用state,必须用在class定义里面,setState修改,可以简写,setState是异步
state = {
name: 'lixin'
}
<h1>{this.state.name}</h1>
// 想等异步结束后
this.setState({
name: 'han'
}, () => {
console.log(this.state.name) // 获取到修改后的值
})
2.10 实现v-model
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg : "Hello world"
}
change = function(e) {
console.log(this)
this.setState({
msg: e.target.value
})
}
render() {
return (
<div>
<input type="text" value={this.state.msg} onChange={this.change.bind(this)}/>
<p>{this.state.msg}</p>
</div>
)
}
}
2.11 修改数组时,只可以用map (item,index) => ()
2.12 import PropTypes from 'prop-types'
指定props类型和必填,可以用在class、function组件中
import "./styles.css";
import PropTypes from "prop-types";
export default function App(props) {
return (
<div className="App">
<h1>{props.number}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
App.propTypes = {
number: PropTypes.number.isRequired
};
// 其他类型 https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
// 配置默认props
/**
上述的propTypes方法写在了外部,如果想写在内部,可以使用static
static: 静态方法,即只可以类调用,而实例调用不了 App.a可以 app.a不可以
可以写成下面
*/
export default class App extends Component {
static propTypes = {
number: PropTypes.number.isRequired
};
static defaultProps = {
number: 11
};
render() {
return (
<div className="App">
<h1>{this.props.number}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
2.13 如果函数中用到了this,需要改变this指向
2.14 传值
2.14.1 父传子 props
- props
1. 在构造器中是否需要传递super,有两种情况
/**
1. props可传可不传,但是如果,这个时候使用this.props就会出现问题
2. contructor的作用,使用this.state和修改函数this指向
3. 但是第二条可以使用state、箭头函数替代,所以构造器可写可不写
*/
constructor() {
super()
console.log(this.props)// undefined
}
2.14.2 子传父 跟vue一样,没有$emit就是
2.14.3 跨级传值 context
// 父组件
1. 定义传入数据类型
static childContextTypes = {
num: PropTypes.number
}
2. 传入值赋值
getChildContext() {
return {
numL 666
}
}
// 孙组件
3. 定义接受数据类型
static contextTypes = {
num: PropTypes.number
}
4. 接收值
this.context.num
2.15 生命周期
2.15.1 挂载阶段
2.15.2 更新阶段
2.15.3 卸载阶段
2.15.4 卸载组件(想React.render()一样)
声明周期(新)
import React, { Component } from 'react'
// 父子组件
class Sub extends Component {
UNSAFE_componentWillReceiveProps(props) {
console.log('0-props-更改props') // 第一次传的的不会走这里
}
shouldComponentUpdate(nextprops) {
console.log('1-props-判断是否更改props')
return nextprops.msg !== this.props.msg
}
UNSAFE_componentWillUpdate() {
console.log('2-props-即将更改props')
}
componentDidUpdate() {
console.log('4-props-完成更改props')
}
render() {
console.log('3-props-渲染')
return (
<h1>{ this.props.msg }</h1>
)
}
}
export default class Child extends Component {
constructor(props) {
super(props)
console.log('01-挂载-构造器')
}
state = {
msg: 'lixin'
}
get() {
this.setState({
msg: 'hello'
})
}
UNSAFE_componentWillMount() {
console.log('02-挂载-开始渲染,之后开始render渲染')
}
componentDidMount() {
console.log('04-挂载-渲染结束')
}
shouldComponentUpdate(nextprops, nextstate) {
/**
* 1. 如果返回true,代表更新;否则不更新
* 2. 如果nextstate等于this.state没有必要更新
* 3. 会走渲染那一步,但是不更新数据
*/
console.log('01-state-更新阶段-判断是否更新', nextstate)
return this.state.msg !== nextstate.msg
}
UNSAFE_componentWillUpdate() {
console.log('02-state-更新阶段-即将准备更新')
}
componentDidUpdate() {
console.log('04-state-更新阶段-更新完成')
}
render() {
console.log('03-挂载+更新-渲染中')
return (
<div>
<Sub msg={this.state.msg} />
<p>{ this.state.msg }</p>
<button onClick={ this.get.bind(this) }>更新</button>
</div>
)
}
componentWillUnmount() {
console.log('卸载阶段') // 通过ReactDOM.unmountComponentAtNode()触发
}
}
// 强制更新 不经过setState,不更新任何状态中的数据,不经过shouldComponentUpdate
export default class App extends Component {
shouldComponentUpdate() {
console.log("1"); // 不会输出
return true;
}
UNSAFE_componentWillUpdate() {
console.log("2");
}
componentDidUpdate() {
console.log("4");
}
update = () => {
this.forceUpdate();
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={this.update}>强制更新</button>
</div>
);
}
}
新的生命周期与旧的区别: 少了三个 多了两个
- getDerivedStateFromProps()
- 从props衍生出的状态 必须是static函数且返回null或者object
- 适用场景:state的值取决于props
- getSnapshotBeforeUpdate
- 返回value、null,值可以作为参数传给componentDidUpdate
// getSnapshotBeforeUpdatean 案例
2.16 redux
案例
2.17 函数式组件以及hook
2.17.1函数式组件特点
-
- 没有生命周期
-
- 没有state
-
- 没有this 所以,使用hook代替state、setState,hook你可以写在判断式下面
2.17.2 useState
const [msg, Setmsg] = useState('11111')
2.17.3 useEffect
因为函数式组件没有生命周期,所以用useEffect代替
-
- 如果useEffect后面没有参数的话,代表componentDidMount和componentDidUpdate
-
- 如果是[],代表componentDidMount
-
- 如果是[num1],代表监听num1这个数值变化
-
- 如果callback有返回值,代表卸载阶段
2.17.4 useContext creatContext
-
- 代替props使用,承接上下文
-
- 使用时 也要定义value
-
- 2种用法 第一种
import { createContext, useContext, useState } from "react";
// 1. 定义context
const numContext = createContext();
function Sub(props) {
// 3. 接受context
const { num, index, setNum } = useContext(numContext);
return (
<>
<h2>{index}</h2>
<h2>{num}</h2>
<button onClick={() => setNum((num) => num + 1)}>累加</button>
</>
);
}
export default function App1() {
const [num, setNum] = useState(0);
return (
<>
{/* 2. 定义provider
当value只有一个参数时,一个{}
多个参数时,{{}}
*/}
<numContext.Provider value={{ num, index: 1, setNum }}>
<Sub />
</numContext.Provider>
<h3>{num}</h3>
</>
);
}
第二种
import { createContext } from "react";
const numContext = createContext();
function Sub() {
return (
// 这里不加return 一旦加了,就报错
<numContext.Consumer>
{({ age, name }) => (
<>
<h1>我的姓名是:{name}</h1>
<h2>我的年龄是:{age}</h2>
</>
)}
</numContext.Consumer>
);
}
export default function App2() {
return (
<numContext.Provider value={{ age: 22, name: "Zoe" }}>
<Sub />
</numContext.Provider>
);
}
2.18 ref
import { useState } from "react";
export default function App() {
let [count, setCount] = useState(0);
const v = 11;
const handleAdd = function () {
if (v === 1) {
console.log(v);
setCount((count) => count + 1);
}
};
return (
<div>
<p>{count}</p>
<button onClick={handleAdd}>加一</button>
</div>
);
}
-
- 类组件
- 使用字符串形式(简单,但是不建议使用)
在this中,有一个refs对象,里面是ref的存储值
export default class App extends Component {
click = () => {
console.log(this);
alert(this.refs.input1.value);
};
render() {
return (
<div className="App">
<input ref="input1" placeholder="please input" />
<button onClick={this.click}>Click</button>
</div>
);
}
}
- 回调ref(严格模式下不可用)
export default class App extends Component {
click = () => {
console.log(this);
alert(this.input1.value);
};
render() {
return (
<div className="App">
<input
ref={(a) => {
console.log(a) // 页面加载时就会打印出来,即<input placeholder="please input"></input>
this.input1 = a;
}}
placeholder="please input"
/>
<button onClick={this.click}>Click</button>
</div>
);
}
}
- 问题:使用回调ref时,在DOM更新、渲染时会调用两次,一次输出null,一次输出对应节点,
- 原因:每次更新时,react会重新加载一个内联函数,不知道传的dom节点,所以为null,第二次为真实的dom节点
- 解决方法:使用class里面的函数
- 总结:与上面的区分不大,直接使用回调就可以,但是输出两次,面试可能会问
export default class App extends Component {
state = {
isHot: true
};
click = () => {
console.log(this);
alert(this.input1.value);
};
changeWeather = () => {
this.setState({ isHot: !this.state.isHot });
};
inputRef = (a) => {
console.log(a); // 页面加载时就会打印出来,即<input placeholder="please input"></input>
this.input1 = a;
}
render() {
return (
<div className="App">
<h1>今天天气很{this.state.isHot ? "炎热" : "寒冷"}</h1>
<button onClick={this.changeWeather}>切换</button>
<input
ref={this.inputRef}
placeholder="please input"
/>
<button onClick={this.click}>Click</button>
</div>
);
}
}
- creactRef 推荐使用
this中会增加input1属性、input1是个对象
export default class App extends Component {
input1 = createRef();
click = () => {
console.log(this);
alert(this.input1.current.value);
};
render() {
return (
<div className="App">
<input ref={this.input1} placeholder="please input" />
<button onClick={this.click}>Click</button>
</div>
);
}
}
2.19 空便签与fragment
空标签与fragment一样,但是空标签不可以设置key、className等
import { Fragment } from "react";
export default function App() {
const state = [1, 2, 3, 4];
return (
<Fragment>
{
state.map((item, index) => (
<Fragment key={index}>
<h1>{item}</h1>
</Fragment>
))
}
</Fragment>
);
}
2.20 错误边界
- 只可以用在class 第一种,使用componentDidCatch
// index.js
import ReactDOM from "react-dom";
import Test from "./Test";
import ErrorBoundry from "./ErrorBoundry";
const rootElement = document.getElementById("root");
ReactDOM.render(
<ErrorBoundry>
<Test />
</ErrorBoundry>,
rootElement
);
// Test.js
import { Component } from "react";
export default class Text extends Component {
state = {
value: ""
};
render() {
const value = this.state.value;
if (/^[a-zA-Z]+$/.test(value) || value === "") {
return (
<input type="text"
placeholder="please input something"
value={value}
onChange={this.changeFn.bind(this)}
/>
);
}
}
changeFn(e) {
this.setState({
value: e.target.value
});
}
}
// ErrorBoundry.js
import { Component } from "react";
export default class ErrorBoundry extends Component {
state = {
hasError: false
};
// 捕获错误
componentDidCatch(error, errorInfo) {
this.setState({
error,
errorInfo,
hasError: true
});
}
render() {
if (this.state.hasError) {
return <h1>出错了!!!!!!</h1>;
} else {
return this.props.children;
}
}
}
第二种
// ErrorBoundry.js 其他不变
import { Component } from "react";
export default class ErrorBoundry extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError() {
return {
hasError: true
};
}
// 捕获错误
render() {
if (this.state.hasError) {
return <h6>出错了!!!!!!</h6>;
} else {
return this.props.children;
}
}
}
2.21 PureComponent
类似shouldComponentUpdate
局限性:
- 只可以进行浅比较
// shouldComponentUpdate操作
import { Component } from "react";
class Sub extends Component {
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.num === 1) {
return false;
}
return true;
}
render() {
return <h2>{this.props.num}</h2>;
}
}
export default class App extends Component {
state = {
num: -1
};
add() {
this.setState({
num: this.state.num + 1
});
}
render() {
return (
<>
<Sub num={this.state.num} />
<button onClick={this.add.bind(this)}>添加</button>
</>
);
}
}
// 使用PureComponent
class Sub extends PureComponent {
render() {
return <h2>{this.props.num}</h2>;
}
}
2.22 高阶组件
- 高阶组件:函数里面返回组件,封装的思想
- 高姐函数:函数里面返回函数
import { Component } from "react";
// 1- 定义高阶函数
// 此函数中,不需要定义高阶组件的名字,是通过参数传递的,第一个参数为组件名字
// 函数名字是大驼峰
function hdcFunc(ComName, name, age) {
return class extends Component {
render() {
return <ComName name={name} age={age} />
}
};
}
// 2-定义使用高姐函数的组件
class Sub1 extends Component {
render() {
return (
<>
<h2>我的名字是: {this.props.name}</h2>
<h2>我的年龄是: {this.props.age}</h2>
</>
);
}
}
// 定义使用高姐函数的组件
class Sub2 extends Component {
render() {
return (
<>
<h2>我的名字是: {this.props.name}</h2>
<h2>我的年龄是: {this.props.age}</h2>
</>
);
}
}
// 3-定义使用在根组件中使用的组件
const MySub1 = hdcFunc(Sub1, "lixin", 22);
const MySub2 = hdcFunc(Sub2, "Zoe", 24);
export default class App extends Component {
render() {
return (
<>
<MySub1 />
<MySub2 />
</>
);
}
}
2.23 懒加载 (LazyLoad + Suspense)
// Child.js
import { Component } from "react";
export default class Child extends Component {
render() {
return <h2>加载完毕</h2>;
}
}
// App.js
import React, { Component, Suspense } from "react";
const Child = React.lazy(() => import("./Child"));
export default class App extends Component {
render() {
return (
<div>
<Suspense fallback={<h2>loading....</h2>}>
<Child />
</Suspense>
</div>
);
}
}
2.24 context
2.24.1 定义
在单层父子通信中使用props,多层可以用context
2.24.2 使用方法
-
- 先定义context
const UserContext = React.createContext('dabaixin')
- 先定义context
-
- 定义context传输方 使用context.Provider,定义传输值
function App(){
return(
<UserContext.Provider value=“11111”>
<Slider/> /*中间组件*/
</UserContet.Provider>
)
}
funciton Slider(){
return <Info/>
}
-
- 定义接收方,两种方式
- 3.1 函数式组件 使用consumer
function Info() {
return(
<UserContext.Consumer>
{
value => (
<button>{value}</button>
)
}
</UserContext.Consumer>
)
}
- 3.2 class组件 必须加static contextType = UserContext;
class Info extends React.Component {
static contextType = UserContext;
render() {
return <button>{this.context}</button>;
}
}
2.26 包含与继承
2.26.1 包含
- 使用props.children
function Test(props) {
return
<div>
<h1>这是组合</h1>
{props.children}
</div>;
}
export default function App() {
return (
<div className="App">
<Test>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</Test>
</div>
);
}
2.26.2 继承
没有用到过
2.27 JSX进阶
<div>
{props.messages.length && <MessageList messages={props.messages} />
}
</div>
像上例,如果messages为空数组,React也会渲染.因为,0时falsy值,会被渲染.可以写成以下格式
<div>
{props.messages.length > 0 && <MessageList messages={props.messages} />
}
</div>
2.28 Portals
- 定义
- 使用React.creatPortal(child, element) 可以把child子组件挂载到element上面
- 例子
// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {showModal: false};
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({showModal: true});
}
handleHide() {
this.setState({showModal: false});
}
render() {
const modal = this.state.showModal ? (
<Modal>
<div className="modal">
This is being rendered inside the #modal-container div.
<button onClick={this.handleHide}>Hide modal</button>
</div>
</Modal>
) : null;
return (
<div className="app">
This div has overflow: hidden.
<button onClick={this.handleShow}>Show modal</button>
{modal}
</div>
);
}
}
ReactDOM.render(<App />, appRoot);
2.29 Profiler
可以看到渲染一个组件的时间 id等
import { Fragment, Profiler } from "react";
import "./styles.css";
function Modal(props) {
return (
<div id="modal">
<h1>This is modal</h1>
{props.cihldren}
</div>
);
}
export default function App() {
function onRenderCallback(
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) {
// 合计或记录渲染时间。。。
console.log(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
);
}
return (
<Fragment id="app-element">
<Profiler id="demo" onRender={onRenderCallback}>
<Modal>
<p>fhgweoihfo</p>
</Modal>
</Profiler>
</Fragment>
);
}
// 输出结果
demo mount 1.8000000715255737 0.5 671.6000000238419 674.1000000238419 Set {}
2.30 受控组件和非受控组件
- 非受控组件: 对于值而言,现用现取
- 受控组件: 把value放在state里面,优点:减少ref的使用
2.31 函数柯里化
- 事件回调不可以写(),否则返回的是值,但是可以利用闭包的想法
- 高阶函数
2.32 diff算法
diff算法最重要的是key的使用
- 常见面试题
- (1)react/vue 中的key有什么作用?(key的原理是什么?)
- (2)为什么遍历列表时,key最好不要用index 答:key是虚拟DOM对象的标识,在更新显示时起着重要的作用。当数据发生变化时,react会根据新数据渲染出新的虚拟DOM,同时与旧的虚拟DOM进行比较,如果key不同时,会渲染新的真实DOM;不同则比较,若内容不同时,则渲染出新的真实DOM。 使用index作为key时,可能会发生以下场景:
- 当数据发生逆序添加、逆序删除等破坏顺序的行为时
- 会产生没必要的真实DOM的更新 ---> 效率降低
- 如果结构中包含输入类的DOM
-
会产生错误的DOM更新 ---> 界面有问题
问题:
三 构建react项目
- 安装scss
npm install sass-loader node-sass --save-dev
npm install --save-dev sass-resources-loader
// 在package.json同级文件下新建config-overrides.js
const { override, adjustStyleLoaders } = require("customize-cra");
module.exports = override(
// ...其他配置...
adjustStyleLoaders(rule => {
if (rule.test.toString().includes("scss")) {
rule.use.push({
loader: require.resolve("sass-resources-loader"),
options: {
resources: "./src/assets/scss/output.scss"
//这里是你自己放公共scss变量的路径
}
});
}
})
);
npm run start即可
使用index.modules.css
- 使用绝对路径@
-
- 添加git add . 和git commit
-
- 安装npm run eject
-
- 在config文件夹下,webpack.config.js,
alias: {
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
'@': path.resolve('src'), //添加该语句
},
图片使用绝对路径
import imgSrc from '@/assets/guide.jpg'
<img src={imgSrc} alt="引导页面图片" />
项目git地址,请多指教,谢谢!
gitee.com/bigwhitexin…