前端入门:快速了解React|青训营笔记

148 阅读12分钟

这是我参与「第四届青训营 」笔记创作活动的的第16天

React的基本使用

React的安装

npm i react react-dom

react包提供创建元素和组件的功能

react-dom包提供dom相关功能,将创建好的元素和组件渲染到页面中。

React的使用

  1. 引用react和react-dom两个js文件
  2. 创建react元素
  3. 渲染react元素到页面中
//1.
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<div id = "root"></div>
<script>
    //2.(null处为元素属性,没有则写null)
    const title = React.createElement('h1',null,'hello')
    //3.
    ReactDOM.render(title,document.getElementById('root'))
</script>

React脚手架的使用

  1. 初始化项目:npx create-react-app my-app (my-app为项目的名称)
  2. 启动程序:cd my-app 然后 npm start
  3. 导入react:在index.js 文件里(基于脚手架的使用,导入方式不同)
import React from 'react'
import ReactDOM from 'react-dom'const title = React.createElement('h1',null,'hello')
ReactDOM.render(title,document.getElementById('root'))

JSX

是javaScript XML的缩写

常见注意点

  1. React元素的属性名使用驼峰命名法
  2. 特殊属性名:class->className、for->htmlFor、tabindex->tabIndex
  3. 没有子节点的React元素可以用 /> 结束。
  4. 使用小括号包裹JSX
const title = (
    <h1 className="title">
        Hello JSX
        <span />hah
    </h1>)

嵌入js表达式

const name = 'jack'
const dv = (
    <div>你好,我是{name}</div>)

{}里可以使用任意js语句,但不能出现表达式。如:if/for;但可以使用函数

JSX的列表渲染

使用map方法

const songs = [
    {id:1, name: 'Alan'}
    {id:2, name: 'jack'}
    {id:3, name: 'Alis'}
]
​
const list = (
    <ul>
        {songs.map(item => <li key = {item.id}>{itme.name}</li> )}
    </ul>)

JSX的样式处理

  1. 行内样式
<p style={{color: 'red',backgroundColor: 'skyblue'}}>样式处理</p>
  1. 类名----className(推荐使用)
import './css/index.css'
​
<p className='title'>样式处理</p>

组件基础

函数创建组件

  • 使用JS的函数或箭头函数创建组件
  • 函数名称必须以大写字母开头
  • 必须有返回值,表示该组件的结构
  • 如果返回值为null,表示不渲染任何内容
function Hello () {
    return (
        <div>这是一个组件</div>
    )
}
ReactDOM.render(<Hello />,document.getElementById('root'))

使用类创建组件

  • 类名称必须大写字母开头
  • 组件一个继承React.Component父类,从而可以使用父类中提供的方法和属性
  • 类组件必须提供render()方法
  • render()方法必须有返回值,表示该组件的结构
class Hello extends React.Component {
	render() {
		return <div>这是一个组件</div>
	}
}

ReactDOM.render(<Hello />,document.getElementById('root'))

抽离为独立JS文件

  1. 创建Hello.js(文件名为组件名)
  2. 在Hello.js中导入React
  3. 创建组件(函数或类)
  4. 在Hello.js中导出该组件
  5. 在index.js中导入Hello组件
  6. 渲染组件
//Hello.js
import React from 'react'
class Hello extends React.Component {
	render() {
		return <div>这是一个组件</div>
	}
}
//导出Hello组件
export default Hello
//index.js
import Hello from "./Hello"
//渲染导入的Hello组件
ReactDOM.render(<Hello />,document.getElementById('root'))

事件绑定

基本语法

  • onClick={()=>{}}
  • React事件采用驼峰命名法

类组件绑定事件:

class App extends React.Component {
	handleClick() {
		console.log('hello')
	}
	render() {
		return (
		<button onClick={this.handleClick}></button>
		)
	}
}

函数组件绑定事件:

function App () {
	function handleClick() {
		console.log('hello')
	}
	return (
		<button onClick={handleClick}>点我</button>
	)
}

事件对象

React中的事件对象叫做合成事件

function handleClick(e) {
	e.preventDefault()
	console.log('事件对象',e)
}
<a onClick={handleClick}>点我</a>

有状态组件和无状态组件

  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 状态即数据
  • 函数组件没有自己的状态,只负责数据展示(静)
  • 类组件又自己的状态,负责更新UI,让页面’动‘起来。

state的基本使用

state值是一个对象,表示一个组件内可以有多个数据

class Hello extends React.Component {
	constructor() {
		super()
		this.state = {
			count:0
		}
	}
	render() {
		return <div>这是一个组件,{this.state.count}</div>
}
//简化语法
class Hello extends React.Component {
	state = {
		count:0
	}
	render() {
		return <div>这是一个组件,{this.state.count}</div>
	}
}

setState()修改状态

  • 状态是可变的
  • 不能直接修改state中的值,这是错误的
//正确
this.setState({
	count:this.state.count+1
})
//错误
this.state.count += 1

JSX抽离事件处理

事件绑定this指向

  1. 箭头函数
class Hello extends React.Component {
	//数据
	state = {
		count:0
	}
	//事件处理程序
	onIncrement() {
		//这里的this是指向的是触发事件里箭头函数里的this
		this.setState({
			count:this.state.count + 1
		})
	}
	render() {
		return (
			<div>
				<h1>计数器:{this.state.count}</h1>
				//这里的this又指向了render()方法里的实例,即组件Hello的实例
				<button onClick={()=>this.onIncrement()}>+1</button>
			</div>
		)
	}
}
  1. Function.prototype.bind()

将事件处理程序中的this和组件实例绑定在一起

class Hello extends React.Component {
	constructor() {
		super()
		this.state = {
			count:0
		}
		this.onIncrement = this.onIncrement.bind(this)
	}
	onIncrement() {
		//这里的this是指向的是触发事件里箭头函数里的this
		this.setState({
			count:this.state.count + 1
		})
	}
	render() {
		return (
			<div>
				<h1>计数器:{this.state.count}</h1>
				//这里的this又指向了render()方法里的实例,即组件Hello的实例
				<button onClick={this.onIncrement}>+1</button>
			</div>
		)
}
  1. class的实例方法 (推荐使用)
class Hello extends React.Component {
	//数据
	state = {
		count:0
	}
	//事件处理程序
	onIncrement = () => {
		//这里的this是指向的是触发事件里箭头函数里的this
		this.setState({
			count:this.state.count + 1
		})
	}
	render() {
		return (
			<div>
				<h1>计数器:{this.state.count}</h1>
				//这里的this又指向了render()方法里的实例,即组件Hello的实例
				<button onClick={this.onIncrement}>+1</button>
			</div>
		)
	}
}

表单处理

受控组件

class Hello extends React.Component {
	state = {
		txt: ''
	}
	handleChange = e => {
		this.setState({
			txt:e.target.value
		})
	}
	render() {
		return (
			<div>
				<input type="text" value={this.state.txt} onChange={this.handleChange}/>
			</div>)
	}
}

文本框、、下拉框、富文本框使用方法抑制。

复选框:

state = {
	isCheck: false
}

handleChecked = e => {
	this.setState({
		isChecked: e.target.checked
	})
}
render() {
	return (
		<div>
			<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked}>	
		</div>)
}

多表单元素优化

  • 给表单元素添加name属性,名称与state相同
  • 根据表单元素类型获取对应值
  • 在change事件处理程序中通过[name]来修改对应的state
class Hello extends React.Component {
	state = {
		txt: '',
		content: '',
		city: '',
		isCheck: false
	}
	handleForm = e => {
		//获取DOM对象
		const target = e.target
		//根据类型获取值
		const value = target.type === "checkbox" 
			?target.checked
			:target.value
		//获取name
		const name = target.name
		this.setState({
			[name] : value
		})
	}
	render() {
		return (
			<div>
				<input name="text" type="text" value={this.state.txt} onChange={this.handleForm}/>
				<textarea name="content" value={this.state.content} onChange={this.handleForm}></textarea>
				<select name="city" value={this.state.city} onChange={this.handleForm}>
					<option value="bj">北京</option>
					<option value="sh">上海</option>
					<option value="gz">广州</option>
				</select>
				<input name="isChecked" type="checkbox" checked={this.state.isChecked} onChange={this.handleForm}/>
			</div>)
	}
}
//优化
const {txt, content, city, isChecked} = this.state
return (
			<div>
				<input name="text" type="text" value={txt} onChange={this.handleForm}/>
				<textarea name="content" value={content} onChange={this.handleForm}></textarea>
				<select name="city" value={city} onChange={this.handleForm}>
					<option value="bj">北京</option>
					<option value="sh">上海</option>
					<option value="gz">广州</option>
				</select>
				<input name="isChecked" type="checkbox" checked={isChecked} onChange={this.handleForm}/>
			</div>)
	}

非受控组件

(不推荐使用)

  1. 调用ReactcreateRef()方法创建一个ref对象
  2. 将创建好的对象添加到文本框中
  3. 通过ref对象获取到文本框的值
class Hello extends React.Component {
	constructor() {
		super()
		//1.
		this.txtRef = React.createRef()
	}
	//获取文本框的值
	getTxt = () => {
		//3.
		console.log(this.txtRef.current.value);
	}
	render() {
		<div>
			//2.
			<input type="text" ref={this.txtRef} />
            <button onClick={this.getTxt}>获取文本框的值</button>
		</div>
}

props

  • 组件是封闭的,要接收外部数据一个通过props来实现
  • props的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性

函数组件

通过参数props接收数据

function Hello (props) {
	console.log(props)
	return (
		<div>接收到数据:{props.name}</div>
	)
}
ReactDOM.render(<Hello name="jack" age={19}  />,document.getElementById('root'))

类组件

通过this.props接收数据

//接收数据
class Hello extends React.Component {
	render() {
		return {
			<div>接收到的数据:{this.props.age}</div>
		}
	}
}
//传递数据
ReactDOM.render(<Hello name="jack" age={19}  />,document.getElementById('root'))

props的特点

  • 可以传递任意类型的数据
<Hello
colors={['red', 'green', 'blue']}
fn={()=>console.log('hello')}
tag={<p>这是一个p标签</p>}
/>
  • 是只读对象,智能读取属性的值,无法修改
  • 如果写了构造函数,应该将props传递给super()
class Hello extends React.Component {
	constructor(props) {
		super(props)
		this.state = {
			count:0
		}
	}
	render() {
		return <div>接收到的数据:{this.props.age}</div>
}

深入

  1. children 属性
  • 表示组件标签的子节点。当组件标签有子节点时,props就会有该属性。
  • 和普通的props一样,值可以是任意值(文本、React元素、组件、函数)
function Hello (props) {
	return {
		<div>
			组件子节点:{props.children}
		</div>
	}
}
ReactDOM.render(<Hello>我是子节点</Hello>,document.getElementById('root'))
const Hello = props => {
	props.children()
	return {
		<div>
			组件子节点
		</div>
	}
}
ReactDOM.render(<Hello>我是子节点</Hello>,document.getElementById('root'))
  1. props 校验
  • 安装包:npm i prop-types
  • 导入包
import PropTypes from 'prop-types'
  • 使用
Hello.propTypes = {
	colors:PropTypes.array
}
  • 常见的约束类型

    • array、bool、func、number、object、string、element
    • 必填项:isRequired
    • 特定结构的对象:shape({})
App.propTypes = {
	a: PropTypes.number,
	fn: PropTypes.func.isRequired,
	tag: PropTypes.element,
	filter: PropTypes.shape({
		area: PropTypes.string,
		price: PropTypes.number
	})
}
  • 默认值
App.defaultProps = {
	pageSize: 10
}

组件通讯

父传子

class Hello extends React.Component {
	state = {lastName: '王'}
	render() {
		return {
		<div>接收到的数据:<Child name={this.state.lastName} /></div>
		}
	}
}

子传父

class Parent wxtends React.Component {
	getChildMsg = (msg) => {
		console.log('接收到子组件数据',msg)
	}
	render() {
		return(
			<div>
				子组件:<Child getMsg = {this.getChildMsg} /></div>
		)
	}
}
class Child extends React.Component {
	state = {childMsg: 'React'}
	handleClick = () => {
		this.props.getMsg(this.state.childMsg)
	}
	return (
		<button onClick={this.handleClick}>点我</button>
	)
}

兄弟组件

class Child extends React.Component {
	state = {
		count:0
	}
	onIncrement = () => {
		this.setstate({
			count: this.state.count + 1
		})
	}
	reder() {
		return (
			<div>
				<Child1 count = {this.state.count} />
                <Child2 onIncrement={this.onInrement} />
			</div>
		)
	}
}
const Child1 = props => {
	return <h1>计数器:{props.count}</h1>
}
const Child2 = props => {
	return <button onClick={() => props.onIncrement()}>+1</button>
}

多层嵌套

  • 创建两个组件
const { Provider, Consumer } = React.createContext ()
  • 使用Provider组件作为父节点,设置value属性传递数据
reder() {
		return (
			<Provider value="pink">
				<div>
					<Child />
				</div>
			</provider>
		)
	}
  • 调用Consumer组件接收数据
const Child5 = props => {
	return (
		<div>
			<Consumer>
				{data = > <span>接收到的数据:{data}</span>}
			</Consumer>
		</div>
	)
}

生命周期

创建时

挂载阶段:钩子函数按顺序执行

  1. constructor
  • 创建组件时,最先执行
  • 初始化state
  • 为事件处理程序绑定this
  1. render
  • 每次组件渲染都会触发
  • 渲染UI(不能调用setState())
  1. componentDidMount
  • 组件挂载后
  • 发送网络请求
  • DOM操作

更新时

更新阶段:按钩子函数顺序执行

  1. render()

组件接收到新的props、setState()、forceUpdate()都会触发render()

  1. componentDidUpdate()
  • 操作DOM
  • 发送网络请求(必须放在一个if条件中)
componentDidUpdate(prevProps) {
	if(prevProps.count !== this.props.count) {
		//this.setState({})
		//发送ajax请求
	}
}

卸载时

卸载阶段:组件从页面消失

componentWillUnmount:执行情理工作(如:清理定时器)

class Counter extends React.Component {
	componentDidMount() {
		this.timerId = setInerval(() => {
			console.log('定时器正在执行~')
		},500)
	}
	render() {}
	componentWillUnmount() {
		clearInterval(this.timerId)
	}
}

render props

<Mouse render = {(mouse) => {
	<p>{mouse.x},{mouse.y}</p>
}}>

class Mouse extends React.Commponent {
	state = {
		x:0,
		y:0
	}
	handleMouseMove = e => {
		this.setState({
			x:e.clientX,
			y:e.clientY
		})
	}
	componentDidMount() {
		window.addEventListener('mousemove',this.handleMouseMove)
	}
	render() {
		return this.props.render(this.state)
	}
}
class App extends React.Commponent {
	render() {
		return (
			<div>
				<Mouse 
					render={mouse=>{
						return (
						<p>鼠标位置:{mouse.x}{mouse.y}</p>
						)
						}
					}
				/>
			</div>)
	}
}

children代替render属性

<Mouse>
	{({x,y}) => <p>鼠标位置:{x},{y}</p>}
</Mouse>
//组件内部
this.props.children(this.state)

高阶组件

  1. 创建高阶组件
  2. 函数参数大写字母开头,作为要渲染的组件
  3. 函数内部创建一个类组件,提供复用的状态逻辑代码
  4. 通过props传递给参数组件
  5. 设置displayName
function WithMouse (WrappedComponent) {
	class Mouse extends React.Component {
		state = {
			x:0,
			y:0
		}
		handleMouseMove = e => {
			this.setState({
				x:e.clientX,
				y:e.clientY
			})
		}
		//控制鼠标状态的逻辑
		componentDidMount() {
			window.addEventListener('mousemove',this.handleMouseMove)
		}
		componentwillUnmount() {
			window.removeEvnetListener('mousemove',this.hanleMouseMove)
		}
		render() {
			return <WrappedComponent {...this.state} {...this.props}>
		}
	}
	//设置displayName
	Mouse.displayName = 'WithMouse${getDisplayName(WrappedComponent)}'
	return Mouse
}
//子组件
const Position = props => (
	<p>
		鼠标当前位置:(x:{props.x},y:{props.y})
	</p>
)
//获取增强好的组件
const MousePosition = withMouse(Position)

class Hello extends React.Component {
	render() {
		return (
			<div>这是一个组件</div>
			<MousePosition />
		)
	}
}

ReactDOM.render(<Hello />,document.getElementById('root'))

React原理揭秘

setState()说明

  • 是异步更新数据的
  • 可以调用多次setState,但只会触发一次重新渲染
	onIncrement = () => {
		//这里的this是指向的是触发事件里箭头函数里的this
		this.setState({
			count:this.state.count + 1
		})
		console.log(this.state.count)//输出结果为1
		this.setState({
			count:this.state.count + 1
		})
		console.log(this.state.count)//输出结果为1
	}
	//最终只会渲染一次,render()为2
  • 推荐语法:也是异步更新,但setState()会拿到最新的数据
this.setState((state, props)=> {
	return {
		count:state.count +1
	}
})
this.setState((state, props)=> {
	console.log('第二次调用:',state)//输出为2
	return {
		count:state.count +1
	}
})
console.log(this.state.count)//输出为1
  • 第二个参数
  • 在页面完成重新渲染后立即执行某个操作(和生命周期里的可以互相代替)
this.setState(
	(state,props)=>{},
	()=>{console.log('状态完成更新:',this.state.count)}
	)
console.log(this.state.count)
//结果是先输出1,再输出2

JSX语法的转化过程

JSX语法编译为createElement()再转化为React元素

组件更新机制

过程:父组件重新渲染时,也会重新渲染子组件。但不会影响兄弟组件

组件性能优化

  1. 减轻state:

    • 只存储跟组件渲染相关的数据(count/列表数据/loading)
    • 不做渲染的数据放到this中(如定时器id)
componentDidMount() {
	this.timerId = setInterval(() => {},2000)
}
  1. 避免不必要的重新渲染

使用钩子函数:当父组件更新时,子组件没有任何变化,则通过return false阻止渲染(可以判断this.state和nextState是否相同等方式)

shouldComponentUpdate(nextProps,nextState) {
	console.log('最新状态:',nextState)
	console.log('未更新渲染前的状态:',this.state)
	return true
}

纯组件

  • PureComponent与React.Component功能相似
  • 区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较
  • 原理:纯组件内部通过分别对比前后两次props和state的值,决定是否重新渲染组件
class Hello extends React.PureComponent {
	render() {
	return ()
	}
}
  • 纯组件内部对比时浅层对比,对引用类型来说,只是比较引用地址是否相同

虚拟DOM和Diff算法

组件中只有一个DOM元素更新时,做到部分更新,只把变化的部分重新渲染到页面中

虚拟DOM本质上就是一个JS对象(React元素)

const element = {
	type:'h1',
	props: {
		className:'greeting',
		children:'Hello JSX'
	}
}

对应的html结构

<h1 class="greeting">Hello JSX</h1>

Diff算法执行过程

  1. 初次渲染时,React会根据初始state,创建一个虚拟DOM树
  2. 根据虚拟DOM生成真正的DOM渲染到页面中
  3. 当数据变化后,根据新的数据,创建新的虚拟DOM树
  4. 与上一次得到的虚拟DOM树,使用Diff对比(找不同),得到需要更新的内容
  5. 最终,React只将变化的内容更新到DOM中,重现渲染页面

render()方法调用不意味着浏览器进行重新渲染,而是说明要进行diff

路由

基本使用

  1. 包安装:npm i react-router-dom@5.2
  2. 导入组件
  3. 使用Router组件包裹整个应用
  4. 使用Link组件作为导航菜单(路由入口)
  5. 使用Router组件配置路由规则和要展示的组件(路由出口)
//导入
import { BrowserRouter as Router, Route, Link} from 'react-router-dom'
const First = () => <p>页面一的内容</p>
const APP = () => (
//使用Router组件包裹整个应用
	<Router>
		<div className="APP">
			<h1>React路由基础</h1>
			//指定入口
			<Link to="/first">页面一</Link>
			//指定出口
			<Router path="/first" component={First}/>
		</div>
	</Router>	
)
<Router>
	<div className="APP">....省略</div>
</Router>

组件的说明

  • Router组件:包裹整个应用,一个React应用只需要使用一次
  • 两种常用Router:HashRouter和BrowserRouter
  • HashRouter:使用URL的哈希值实现(localhost:3000/#/first)
  • (推荐)BrowserRouter:使用H5的history API实现(localhost:3000/first)
  • Link组件:用于指定导航链接(a标签)
  • Route组件:指定路由展示组件相关信息

路由的执行过程

  1. 点击Link组件,修改了浏览器地址栏中的url
  2. React路由监听到地址栏url的变化
  3. React路由内部遍历所有Router组件,使用路由规则(path)与pathname进行匹配
  4. 当路由规则能够匹配地址栏中的pathname时,就展示该Router组件的内容

编程式导航

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link} from 'react-router-dom'
class Login extends React.Component {
	handleLogin = () => {
		this.props.history.push('home')
	}
	render() {
		return (
			<div>
				<p>登录页面:</p>
				<button onClick={this.handleLogin}>登录</button>
			</div>
		)
	}
}

const Home = (props) => (
	const handleBack = () => {
		//返回上一个页面
		props.history.go(-1)
	}
	return (
		<div>
			<h2>我是后台首页</h2>
			<button onClick={handleBack}>妇女会登录页面按钮</button>
		</div>
	)
)

const App = () => (
	<Router>
		<div>
			<h1>编程式导航:</h1>
			<Link to="/Login">去登录页面</Link>
			<Route path="/login" component={Login} />
			<Route path="/home" component={Home} />
		</div>
	</Router>
)
RecatDOM.render(<App />,document.getEIementById('root'))

默认路由

进入页面时就会匹配的路由

<Router path="/" component={Home} />

匹配模式

  1. 模糊匹配模式

只要pathname以path开头就会匹配成功

可以匹配到
  1. 精确匹配(推荐)

只有当path和pathname完全相同时才会匹配

项目准备

项目搭建

  1. 项目初始化
  • 初始化项目:npx create-react-app my-app (my-app为项目的名称)
  • 启动程序:cd my-app 然后 npm start
  • 导入react:在index.js 文件里(基于脚手架的使用,导入方式不同)
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'

ReactDOM.render(<App />,document.getElementById('root'))
  • 调整App.js文件
import React from 'react'

function App() {
	return <div className="App">项目根组件</div>
}

export default App
  • 调整项目中src目录结构:

src/ 项目源码,写项目功能代码

assets/ 资源 (图片、字体图标等)

components/ 公共组件

pages/ 页面

utils/ 工具

App.js 根组件(配置路由信息)

index.css 全局样式

index.js 项目入口文件(渲染根组件、导入组件库)

  1. 本地接口部署
  • 启动Mysql,创建数据库,导入数据
  • cmd打开项目文件夹:输入命令行 npm start
  • 接口测试:http://localhost:8080

组件库

  1. 安装:npm install --save antd-mobile
  2. 在App.js根组件中导入要使用的组件
  3. 渲染组件
  4. 在index.js中导入组件库样式
import { Button } from 'antd-mobile'
<Button />
import 'antd-mobile/dist/antd-mobile.css'

基础路由配置

  1. 包安装:npm i react-router-dom@5.2
  2. 导入组件
  3. 在pages文件夹中创建Home/index.js和其他组件(根据项目需要)
  4. 使用Route组件配置首页和其他(根据项目需要)页面
//在Home/index.js文件中
import React from 'react'
export default class Home extends React.Component {
	render() {
		return <div>首页</div>
	}
}
//其他页面的index.js文件基本结构相同,把Home改为其他组件名字即可
//在App.js文件中
import React from 'react'
//导入路由
import { BrowserRouter as Router, Route, Link} from 'react-router-dom'
//导入首页和其他组件(页面级别的组件)
import Home from '.pages/Home'
//导入组件库需要的组件
function App() {
	return (
		<Router>
			<div className="App">
				{/*导航菜单*/}
				<Link to="/home">首页</Link>
				{/*路由配置*/}
				<Route path="/home"></Route>
			</div>
		</Router>
	)
}

export default App

\