这是我参与「第四届青训营 」笔记创作活动的的第16天
React的基本使用
React的安装
npm i react react-dom
react包提供创建元素和组件的功能
react-dom包提供dom相关功能,将创建好的元素和组件渲染到页面中。
React的使用
- 引用react和react-dom两个js文件
- 创建react元素
- 渲染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脚手架的使用
- 初始化项目:
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'
const title = React.createElement('h1',null,'hello')
ReactDOM.render(title,document.getElementById('root'))
JSX
是javaScript XML的缩写
常见注意点
- React元素的属性名使用驼峰命名法
- 特殊属性名:class->className、for->htmlFor、tabindex->tabIndex
- 没有子节点的React元素可以用 /> 结束。
- 使用小括号包裹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的样式处理
- 行内样式
<p style={{color: 'red',backgroundColor: 'skyblue'}}>样式处理</p>
- 类名----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文件
- 创建Hello.js(文件名为组件名)
- 在Hello.js中导入React
- 创建组件(函数或类)
- 在Hello.js中导出该组件
- 在index.js中导入Hello组件
- 渲染组件
//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指向
- 箭头函数
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>
)
}
}
- 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>
)
}
- 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>)
}
非受控组件
(不推荐使用)
- 调用ReactcreateRef()方法创建一个ref对象
- 将创建好的对象添加到文本框中
- 通过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>
}
深入
- 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'))
- 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>
)
}
生命周期
创建时
挂载阶段:钩子函数按顺序执行
- constructor
- 创建组件时,最先执行
- 初始化state
- 为事件处理程序绑定this
- render
- 每次组件渲染都会触发
- 渲染UI(不能调用setState())
- componentDidMount
- 组件挂载后
- 发送网络请求
- DOM操作
更新时
更新阶段:按钩子函数顺序执行
- render()
组件接收到新的props、setState()、forceUpdate()都会触发render()
- 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)
高阶组件
- 创建高阶组件
- 函数参数大写字母开头,作为要渲染的组件
- 函数内部创建一个类组件,提供复用的状态逻辑代码
- 通过props传递给参数组件
- 设置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元素
组件更新机制
过程:父组件重新渲染时,也会重新渲染子组件。但不会影响兄弟组件
组件性能优化
-
减轻state:
- 只存储跟组件渲染相关的数据(count/列表数据/loading)
- 不做渲染的数据放到this中(如定时器id)
componentDidMount() {
this.timerId = setInterval(() => {},2000)
}
- 避免不必要的重新渲染
使用钩子函数:当父组件更新时,子组件没有任何变化,则通过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算法执行过程
- 初次渲染时,React会根据初始state,创建一个虚拟DOM树
- 根据虚拟DOM生成真正的DOM渲染到页面中
- 当数据变化后,根据新的数据,创建新的虚拟DOM树
- 与上一次得到的虚拟DOM树,使用Diff对比(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新到DOM中,重现渲染页面
render()方法调用不意味着浏览器进行重新渲染,而是说明要进行diff
路由
基本使用
- 包安装:npm i react-router-dom@5.2
- 导入组件
- 使用Router组件包裹整个应用
- 使用Link组件作为导航菜单(路由入口)
- 使用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组件:指定路由展示组件相关信息
路由的执行过程
- 点击Link组件,修改了浏览器地址栏中的url
- React路由监听到地址栏url的变化
- React路由内部遍历所有Router组件,使用路由规则(path)与pathname进行匹配
- 当路由规则能够匹配地址栏中的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} />
匹配模式
- 模糊匹配模式
只要pathname以path开头就会匹配成功
可以匹配到- 精确匹配(推荐)
只有当path和pathname完全相同时才会匹配
项目准备
项目搭建
- 项目初始化
- 初始化项目:
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 项目入口文件(渲染根组件、导入组件库)
- 本地接口部署
- 启动Mysql,创建数据库,导入数据
- cmd打开项目文件夹:输入命令行 npm start
- 接口测试:http://localhost:8080
组件库
- 安装:npm install --save antd-mobile
- 在App.js根组件中导入要使用的组件
- 渲染组件
- 在index.js中导入组件库样式
import { Button } from 'antd-mobile'
<Button />
import 'antd-mobile/dist/antd-mobile.css'
基础路由配置
- 包安装:npm i react-router-dom@5.2
- 导入组件
- 在pages文件夹中创建Home/index.js和其他组件(根据项目需要)
- 使用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
\