1、react脚手架的安装
npm i create-react-app -g
创建react项目
creact-react-app 文件名称
注意:在项目中的package.json中的react-script是creact-react-app的核心部件,可以自动构建项目 , 以及编译,相当于webpack傻瓜版
注意:如果需要在项目中使用typescript,那么构建项目的语法是
create-react-app 文件名称 --template typescript
注意:在使用typescript进行react项目开发的时候,需要装的包初步列示如下,也就是说当使用typescript时除了安装所需要的包外,还需要安装声明文件,下面的redux与redux-thunk的声明文件内置了,所以不需要再安装,通常是@types/***, 并且类型声明一般只会在开发过程中使用,所以用--save-d
npm i redux redux-thunk react-redux @types/react-redux react-router-dom @types/react-router-dom
2、react关于jsx语法以及虚拟dom初识
jsx语法
import reactDom from 'react-dom' /** * jsx即javascript + xml把js与html混写的一种语法 * 可以在jsx上添加属性如id='mytest' * 注意如果有需要添加class这个时候就需要把属性名改为className如下 * 如果想使用style,那么就需要用两个花括号进行使用,方法有同vue里的jsx语法 * 如果想使用变量,那么就需要用一个花括号进行修饰如下,也可以修饰一个方法的调用 * */ let name = 'bill' reactDom.render( <div id='mytest' className='test' style={{'color': 'red'}}>this is test {name}</div>, document.getElementById('root') //注意:这里的节点的获取可以使用window.root进行替代 )
在使用jsx进行编写的时候babel会在编译的时候把相当的语法转在一个对象,相当于react的虚拟dom对象
注意:在编写style的时候,React接受一个采用小驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串。这与 DOM 中 style 的 JavaScript 属性是一致的,同时会更高效的,且能预防跨站脚本(XSS)的安全漏洞
const divStyle = { color: 'blue', backgroundImage: 'url(' + imgUrl + ')', };
虚拟dom
import react from 'react' import reactDom from 'react-dom' let name = 'bill' let elem = react.createElement('div', { id: 'mytest', className: 'test', style: {'color': 'red'} }, 'this is test ' + name, react.createElement('span', null, 'ok') ) //子类可以以数组的形式存在也可以以一个参数的形式存在,上面是以一个参数的形式存在,而下面是以一个数组的形式存在 // let elem = react.createElement('div', { // id: 'mytest', // className: 'test', // style: {'color': 'red'} // }, ['this is test ' + name, react.createElement('span', null, 'ok')] ) reactDom.render(elem, document.getElementById('root'))
以上两种方法的效果是一致的,但是第一种方式更容易编写,层次更加分明
注意:在上面第二种方法中的elem中可以获取到属性props, 这个props对象里面有所对应的该节点的属性,同时里面的个children对象,这个children对象包含着所有的子对象,元素的更新,这个时候就会触发react里的diff算法,元素里与之前dom是相同的,那么就不会更新,如果是不想同的,就会触发更新, 如下面的例子
import react from 'react' import reactDom from 'react-dom' setInterval(() => { let elem = react.createElement('div', null, react.createElement('span', null, '时间是:'), new Date().toLocaleTimeString()) reactDom.render(elem, document.getElementById('root')) }, 1000)
以上的例子中,span里的内容是时间是:这个时候更新的只会是时间部份的内容,其他内容不会被更新
3、react组件
我们把页面拆分成若干个独立的部份, 单独编写,单独维护,组件分成两种: 一种是函数式组件,一种是类组件
函数式组件
特点: 1、一个返回普通React元素的函数就是一个合法的React组件; 2、组件需要返回一个并且仅能返回一个React根元素; 3、组件的名称必须以大写字母开头
import reactDom from 'react-dom' //也可以使用解构赋值的形式{name, age}作为形参 let Welcome = (props) => { return <div className='container'> <span>我的名字是</span> <span>{props.name}</span> <span>我的年龄是</span> <span>{props.age}</span> </div> } // reactDom.render(<Welcome name='bill' age='12'/>, document.getElementById('root')) //如果需要引入一个对象的所有值作为所有的属性,那么就可以写作如下的方式 let obj = {name: 'bill', age: 12} reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))
类组件
import react from 'react' import reactDom from 'react-dom' class Welcome extends react.Component { render() { return <div className='container'> <span>我的名字是</span><span>{this.props.name}</span> <span>我的年龄是</span><span>{this.props.age}</span> </div> } } // reactDom.render(<Welcome name='bill' age='12'/>, document.getElementById('root')) //如果需要引入一个对象的所有值作为所有的属性,那么就可以写作如下的方式 let obj = {name: 'bill', age: 12} reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))
注意点: react中相当于vue中的template的写法
//注意如果不需要最外面套一层外层,那么就可以使用以下的两种表达方式,这两种表达方式是一个意思 class Welcome extends react.Component { render() { // return <><span>name:</span>{this.props.name}<span>age:</span>{this.props.age}</> return <react.Fragment><span>name:</span>{this.props.name}<span>age:</span>{this.props.age}</react.Fragment> } } let obj = {name: 'bill', age: 12} reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))
注意: 如果能用函数组件,就使用函数式组件,比较节省性能
函数式组件与类组件的搭配使用
import react from 'react' import reactDom from 'react-dom' const Test = props => { return <> <h1>{props.title}</h1> <button onClick={props.onClick}>按钮</button> </> } class Count extends react.Component{ clickEvent = () => { console.log('are you ok????') } render() { return <div className='container'> <Test title='这个是标题' onClick={this.clickEvent}/> </div> } } reactDom.render(<Count />, document.getElementById('root'))
从受控程度来讲,组件分为
1、非受控组件: 是指DOM元素的值存在DOM元素的内部,不受React控制
2、受控组件:是指DOM元素的值受React状态控制
4、状态
- 组件的数据来源主要有两个地方, 分别是属性对象和状态对象
- 属性是父组件传递过来的(默认属性, 属性校验)
- 状态是自己内部的,改变状态的唯一方式就是setState, 注意setState这个方法是异步的
- 属性和状态的变化都会影响到视图的更新
import react from 'react' import reactDom from 'react-dom' class Clock extends react.Component { constructor(props) { super(props) this.state = { date: new Date().toLocaleTimeString() } } componentDidMount() { //react的生命周期函数 相当于vue里的mounted this.timer = setInterval(() => { //注意这个方法主要传达两层意思, 一层是改变数据, 一层是刷新视图层 this.setState({date: new Date().toLocaleTimeString()}) }, 1000) } render() { return <div>{this.state.date}</div> } } reactDom.render(<Clock />, document.getElementById('root'))
注意:进行state刷新的时候,要使用setState方法,这样才可以刷新视图层,如果是直接赋值,则不会刷新视图层,当然还有一种方法可以达到这种效果,就是this.state.date = new Date().toLocaleTimeString()进行调用后,再使用this.forceUpdate()进行强制页面更新也可以达到类似的效果, 这个方法是react.Component中定义的
setState方法的特性
setState这个方法是异步的
clickEvent = () => { // this.setState({count: this.state.count + 1}) // this.setState({count: this.state.count + 1}) //如果是调用上面的两个方法的话,那么执行该方法后,目标变量只会增加1,因为setState是异步, 注意setState这个方法里的函数接收两个参数,一个是prevState, prevProps // this.setState(state => ({count: state.count + 1})) // this.setState(state => ({count: state.count + 1})) //使用回调的方式可以实现在函数内部变量的引用,有效的避免上面的问题,但是外部还是异步的 this.setState({count: this.state.count + 1}, () => { console.log(this.state.count) }) //也可以使用回调的方式实现同步实现 }
setState方法如果在进行赋值的时候,没有提及的不会被覆盖,其内部实现了提及的更新,或增量更新,而未提及的不动,如果需要进行删除的功能,可以使用undefined或null进行覆盖
函数式组件改变state的方法
import React, {Component, useState} from 'react'; import ReactDom from 'react-dom' class Index extends Component { render() { return ( <div> <Count/> </div> ); } } //函数式组件, 关于变量的改变引起视图层的变化的方法 const Count = () => { let [count, setCount] = useState(100); //返回的是一个值,以及改变这个值的方法 const add = () => { setCount(++ count); } return <div> <span>{count}</span> <button onClick={() => add()}>按钮</button> </div> } ReactDom.render(<Index/>, window.root)
注意:还有一种騒操作
this.state.count ++; //先更新数据 this.setState({}) //实现页面刷新
state在函数式组件中使用的时候有三个注意点
注意点一
import ReactDom from 'react-dom' import {useRef, useState} from "react"; /** * 当快速的点击增加后,再点击输出count的时候,会发现count的值并非是最新的,这个时候就需要用useRef进行辅助 * @returns {*} * @constructor */ const NoticeOne = () => { let [count, setCount] = useState(100) let ref = useRef() const add = () => { setCount(++count) ref.current = count; } const logVal = () => { setTimeout(() => { console.log(count) //输出的值并非是正确的 // console.log(ref.current) //输出的值是正确的 }, 2000) } return <div> <h3>{count}</h3> <button onClick={add}>加</button> <button onClick={logVal}>输出</button> </div> } ReactDom.render(<NoticeOne/>, window.root)
注意点二
import ReactDom from 'react-dom' import {useState} from "react"; /** * 在setCount中使用函数解决异步问题 * @returns {*} * @constructor */ const NoticeOne = () => { let [count, setCount] = useState(100) const add = () => { setCount(++count) } const minus = () => { setTimeout(() => { setCount(num => --num) //使用异步可以解决信息不同步问题 }, 2000) } return <div> <h3>{count}</h3> <button onClick={add}>加</button> <button onClick={minus}>减</button> </div> } ReactDom.render(<NoticeOne/>, window.root)
注意点三:函数式组件中设置对象
import ReactDom from 'react-dom' import {useState} from "react"; /** * 在setCount中使用函数解决异步问题 * @returns {*} * @constructor */ const NoticeOne = () => { let [state, setState] = useState({name: 'yfBill', count: 100}) const add = () => { setState({ ...state, count: ++state.count }) } const minus = () => { setState({ ...state, count: --state.count }) } return <div> <h3>{state.name}{state.count}</h3> <button onClick={add}>加</button> <button onClick={minus}>减</button> </div> } ReactDom.render(<NoticeOne/>, window.root)
注意点四:在react18后,如果要使用setState的同步机制,那么需要按如下的方法进行处理
public changeTitleEvent(): void {
flushSync(() => {
this.setState({
title: 'ok!!!',
});
});
console.log(this.state.title);
}
// 那么这个时候this.state.title拿到的是更改后的数据, flushSync函数里的作法会被当作一次render统一执行
5、react组件的事件绑定
采用react进行事件绑定的时候,注意区别于原生的绑定事件,react绑定事件上采用的是驼峰的写法
在进行绑定的时候,在函数内部的this指向是个问题,有三个方案可以解决this的指向问题,具体如下例子
import react from 'react' import reactDom from 'react-dom' class List extends react.Component { clickEvent = () => { console.log('this指向', this) } render() { //注意这里的click事件的绑定要区别于原生的绑定事件,这里的绑定事件采用的是驼峰的写法 /** * this 的指向修正方案 * 方法一,采用bind方法进行this的修正,如用 onClick={this.clickEvent.bind(this)} 进行调用, 用这种方式,那么如果有参数,那么默认的ev会变成最后一个参数 * 方法二,采用匿名函数的进行修正如: onClick={() => this.clickEvent()}进行调用,如果需要传参,那么就使用该方法,但是同是默认是没有ev的,需要ev onClick={ev => this.clickEvent(ev)} * 方法三,采用ES7的语法进行调用,如本例子,默认会接收到ev, 这种方式不能传递参数 */ return (<react.Fragment> <div>this is list</div> <button onClick={this.clickEvent}>按钮</button> </react.Fragment>) } } reactDom.render(<List />, document.getElementById('root'))
6、react的ref的使用
在react中现官方推荐使用的ref是调用react里的createRef()方法,这个方法会返回一个对象相当于{current: null},在虚拟dom进行编译的时候,检索到该对象的时候会把对应的值传到current上,这时就可以通过调用current获取到指定的dom对象
import react from 'react' import reactDom from 'react-dom' const Test = props => { return <> <h1>{props.title}</h1> <button onClick={props.onClick}>按钮</button> </> } class Count extends react.Component{ constructor(props) { super(props) this.elem = react.createRef(); } clickEvent = () => { console.log(this.elem) //输出 {current: div.container} } render() { return <div className='container' ref={this.elem}> <Test title='这个是标题' onClick={this.clickEvent}/> </div> } } reactDom.render(<Count />, window.root)
注意:以上情况只是针对ref指向的是dom节点,如果ref指向的是类组件,那么获取的是类组件的实例,但是函数式组件需要套用react.forwardRef包一层才可以指定对应的ref如下例子
import react from 'react' import reactDom from 'react-dom' //函数组件 const Test = (props, ref) => { //用react.forwardRef包的函数式组件,接收两个参数,一个是props一个是ref对象 return <div ref={ref}> <h1>{props.title}</h1> <button onClick={props.onClick}>按钮</button> </div> } //类组件 class Check extends react.Component { render() { return <div>this is check</div> } } const TestWrap = react.forwardRef(Test) //用react.forwardRef对函数式组件进行包一层 //也可以用以下的方法替代 // const TestWrap = react.forwardRef((props, ref) => { // return <div ref={ref}> // <h1>{props.title}</h1> // <button onClick={props.onClick}>按钮</button> // </div> // }) class Count extends react.Component{ constructor(props) { super(props) this.elem = react.createRef(); this.check = react.createRef(); } clickEvent = () => { console.log(this.elem, this.check) //输出 {current: div} {current: Check} } render() { return <div className='container'> <TestWrap title='这个是标题' onClick={this.clickEvent} ref={this.elem}/> <Check ref={this.check}/> </div> } } reactDom.render(<Count />, window.root)
高阶组件的ref转发
import React, {
FC,
ReactElement,
useRef,
memo,
ForwardedRef,
forwardRef,
} from 'react';
interface IItemProps {
ref: ForwardedRef<HTMLDivElement>;
}
const Item: FC<IItemProps> = memo(
forwardRef<HTMLDivElement, IItemProps>(
(props: IItemProps, ref): ReactElement => {
return <div ref={ref}>this is Item</div>;
},
),
);
const App: FC = (): ReactElement => {
const inputRef = useRef<HTMLDivElement>(null);
const clickEvent = () => {
console.log(inputRef.current);
};
return (
<div>
<h1>this is App</h1>
<Item ref={inputRef} />
<button onClick={() => clickEvent()}>点击</button>
</div>
);
};
export default App;
注意:在函数式组件中内部使用ref用的对象是useRef
import React, {Component, useRef} from 'react'; import ReactDom from 'react-dom' class Index extends Component { render() { return ( <div> <Item/> </div> ); } } const Item = () => { let elem = useRef() const fn = () => { console.log(elem) //这样就可以获得{current: h1}对象,就可以获得元素了 } return <div> <h1 ref={elem}>this is item</h1> <button onClick={fn}>按钮</button> </div> } ReactDom.render(<Index/>, window.root)
7、在react中实现数据的双向绑定
import react from 'react' import reactDom from 'react-dom' class Comp extends react.Component { constructor(props) { super(props); this.state = { val: 'are you ok???', bindVal: 'yes' } } changeEvent = ev => { console.log(ev.target.value) this.setState({ bindVal: ev.target.value }) } render() { //在input中如果是默认的值可以defaultValue进行默认 //在react中如果实现双重绑定用下在的第二种方法 return <> <input type="text" defaultValue={this.state.val}/> <input type="text" value={this.state.bindVal} onChange={this.changeEvent}/> <button>点击</button> </> } } reactDom.render(<Comp/>, window.root)
8、常见的知识点
import React from 'react'; import ReactDom from 'react-dom' const App = () => { let str = '<h2>this is h2 content</h2>' return <div> <h1>this is App</h1> <div dangerouslySetInnerHTML={{__html: str}}></div> //如果有需要按html的格式进行填充的,那么就需要该属性以这种方式填充 <label htmlFor="abc">haha //因为for是关键字,所以在react中用htmlFor进行替代 <input type="text" id='abc'/> </label> </div> } ReactDom.render(<App/>, window.root)
fragment
在react中,根节点需要包一层元素,但是如果不需要这个元素可以使用fragment进行消除<fragment></fragment>有渲染后就会被替换掉, 也可以使用<></>替代,但是一种情况下不能用<></>替换,即遍历循环的时候,需要填充key, 这个时候就不能用这个语法糖
react的严格模式
react的StrictMode是一个用来突出显示应用程序中潜在问题的工具
- 与Fragment一样,StrictMode不会渲染任何可见的UI;
- 它为后代元素触发额外的检查和警告
- 严格模式检查仅在开发模式下运行;它们不会影响生产构建
react的严格模式检查的是什么
- 识别不安全的生命周期
- 使用过时的ref api
- 检测意外的副作用 (组件会被调用两次,让用户查看是否代码会产生副作用)
9、react组件的懒加载
Suspense是react里的组件