Hello,我是一名软件工程的大三学生,目前正在学习React,记录自己的学习过程,并以此督促自己学习。
目录
开发前的准备
React官网地址
Node版本依赖
创建项目环境命令(工具链)
npx create-react-app my-app()
注意:项目名称不能存在大写
项目结构
安装vsCode插件
ES7 React/Redux/GraphQL/React-Native snippets
JSX语法
React文件后缀
1. js 2. jsx
什么是JSX语法
就是JavaScript + XML(HTML) <user> <name>iwen</name> <age>20</age> </user> 严格:必须是闭合标签 <img />
解析JSX
遇到{}按照JS解析 遇到<>按照HTML解析
元素渲染
条件渲染
项目结构:把不需要的文件del
1. 条件不同的选择 2. 渲染数据时候,如果数据不存在,可以用条件渲染进行判断
Conditional.jsx
import React, { Component } from 'react'
export default class Conditional extends Component {
render() {
const message = {
title:"some data"
}
let flag = true
return (
<div>
{
flag ? '孙悟空' : '六耳猕猴'
}
{
message.title ? <p>{message.title}</p> : <p>have no data</p>
}
</div>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Conditional from './Conditional'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
<Conditional />
</div>
);
列表渲染
1. 渲染列表数据 2. key关键字 每一条数据都需要一个唯一索引 1. 使用index下标 2. 使用每条数据的唯一id(推荐) key不发生变化,则不会重新渲染,key发生变化则需要重新渲染
Lists.jsx
import React, { Component } from 'react'
export default class Lists extends Component {
/**
* render() 渲染函数:提供一个可渲染视图的方案
*/
render() {
const banners = {
"success": true,
"msg": "",
"banner": [
{
id: '1',
"title": "我在爱尔兰",
"content": "爱尔兰(爱尔兰语:Poblacht na hÉireann;英语:Republic of Ireland), 是一个西欧的议会共和制国家,西临大西洋,东靠爱尔兰海,与英国隔海相望,是北美通向欧洲的通道爱尔兰自然",
},
{
id: "2",
"title": "一个人的东京",
"content": "东京(Tokyo)是日本国的首都,是亚洲第一大城市,世界第二大城市。全球最大的经济中心之一。东京的著名观光景点有东京铁塔、皇居、国会议事堂、浅草寺、浜离宫、上野公园与动物园",
},
{
id: "3",
"title": "普罗旺斯的梦",
"content": "普罗旺斯(Provence)位于法国东南部,毗邻地中海和意大利,从地中海沿岸延伸到内陆的丘陵地区,中间有大河“Rhone”流过。自古就以靓丽的阳光和蔚蓝的天空,迷人的地中海和心醉",
},
{
id: "4",
"title": "相约夏威夷之夏",
"content": "夏威夷州位于北太平洋中,距离美国本土3,700公里,总面积16,633平方公里,属于太平洋沿岸地区。首府为檀香山。在1778至1898年间,夏威夷也被称为“三明治群岛”(Sandwich Islands)",
}
]
}
return (
/**
* 如果是单行视图,可不不用增加()
* 如果是多行视图,必须增加()
*/
<div>
<ul>
{
banners.banner.map((ele, index) => {
return (// 如果视图换行了,return后面要添加()
// <div>必须只有一个根元素
// <li key={ele.id}>{ele.title}</li>
// <p>{ele.content}</p>
// </div>
<li key={index}>
<h3>{ele.title}</h3>
<p>{ele.content}</p>
</li>
)
})
}
</ul>
</div>
)
}
}
组件
1. 后缀: .js .jsx 2. 创建组件: 名字遵循大驼峰命名法 3. render函数中返回值需要添加() 4. render中返回的时候,只能存在一个根容器 5. 组件在应用的时候,首字母必须大写
组件嵌套
快捷键:
1. 快速生成一个React基本结构:rcc
Props
页面效果:
MyProps.jsx
import React, { Component } from 'react'
export default class MyProps extends Component {
render() {
const { title, desc, list } = this.props
return (
<div>
<h3>{title}</h3>
<p>{desc}</p>
<div>
{
list ?
<ul>
{
list.map((ele,index)=>{
return <li key={index}>{ele}</li>
})
}
</ul>
: <p>暂无数据...</p>
}
</div>
</div>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import MyProps from './MyProps'
const root = ReactDOM.createRoot(document.getElementById('root'));
let detatils = {
title:"详情页",
desc:"这是一个详情页描述",
list:["测试1","测试2"]
}
let user = {
title:"用户中心",
desc:"这是一个用户中心"
}
root.render(
<div>
<MyProps title={detatils.title} desc={detatils.desc} list={detatils.list}/>
<MyProps title={user.title} desc={user.desc}/>
</div>
);
State
1. 修改State:this.setState({})
MyState.jsx
import React, { Component } from 'react'
export default class MyState extends Component {
/**
* 定义State方式
* 1. 简单方式(如果要修改状态的时候,可能出现错误)
* 2. 构造函数方式(推荐)
*/
// state = {
// userInfo:{
// name:"iwen",
// age:20
// }
// }
/**
* props和state的区别
* props:对外
* state:对内
* props和state是类似的,不同的是State是私有的,而且完全受控于当前组件
*/
constructor(props) {
super(props)
this.state = {
userInfo: {
name: "@前端练习生",
age: 20
}
}
}
render() {
let userInfo = this.state.userInfo
return (
<div>
<h3>state:状态</h3>
<div>
<p>{userInfo.name}</p>
<p>{userInfo.age}</p>
</div>
</div>
)
}
}
事件处理
-
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
-
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
-
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
-
-
事件是可以修改状态的
Events.jsx
import React, { Component } from 'react'
import './style.css'
/**
* .box{
width: 150px;
height: 150px;
background: rgb(175, 175, 235);
}
*/
export default class Events extends Component {
constructor(props){
super(props)
this.state = {
flag :false
}
this.changeFlagHandle = this.changeFlagHandle.bind(this)//在构造函数里改变this,不会报错
}
mouseMoveHandle(){
console.log('I LOVE REACT')
}
clickHandle(){
console.log("我被点了")
}
// changeFlagHandle(){//报错了 Events.jsx:17 Uncaught TypeError: Cannot read properties of undefined (reading 'setState')
// console.log(this)//undefined
// this.setState({
// flag:!this.state.flag
// })
// }
// changeFlagHandle =()=>{//使用箭头函数不会报错
// this.setState({
// flag:!this.state.flag
// })
changeFlagHandle(){//在构造函数里改变this,不会报错
this.setState({
flag:!this.state.flag
})
}
render() {
return (
<div>
<h3>事件处理</h3>
<div className='box' onMouseMove={this.mouseMoveHandle}></div>
<button onClick={this.clickHandle}>点我</button>
<div>
{/* <button onClick={this.changeFlagHandle.bind(this)}>ChangeFlag</button> */}
<button onClick={this.changeFlagHandle}>ChangeFlag</button>
{
this.state.flag ? '孙悟空' : '六耳猕猴'
}
</div>
</div>
)
}
}
添加样式
1. class -> className
事件处理续
-
添加事件
-
研究一下关于改变State中的this问题
-
在视图结构中改变this指向:.bind(this)
-
将事件函数改成箭头函数(推荐)
-
在构造函数中,改变this指向:this.clickHandle = this.clickHandle.bind(this)
-
-
事件传递参数
-
Event对象
MyEvents.jsx
import React, { Component } from 'react'
export default class MyEvents extends Component {
constructor(){
super();
this.state = {
flag:false,
names:["jack","jerry","tom"]
}
this.clickHandle = this.clickHandle.bind(this)
}
clickHandle(){
this.setState({
flag:!this.state.flag
})
}
// clickHandle = () =>{
// this.setState({
// flag:!this.state.flag
// })
// }
clickListHandle = (ele,e) =>{
console.log(ele);
console.log(e);
}
baseClickHandle = (event) =>{
console.log(event);
}
render() {
// 解构赋值
const { flag,names } = this.state;
return (
<div>
<h3>事件处理续</h3>
{/* <button onClick={ this.clickHandle.bind(this) }>按钮</button> */}
<button onClick={ this.clickHandle }>按钮</button>
{
flag ? "孙悟空" : "六耳猕猴"
}
<div>
<ul>
{
names.map((ele,index) =>{
return <li onClick={ (e) => this.clickListHandle(ele,e) } key={index}>{ ele }</li>
})
}
</ul>
</div>
<button onClick={ this.baseClickHandle }>按钮2</button>
</div>
)
}
}
state可能是异步的
从视图和下面的代码,可以看到一个打印0,一个视图呈现1,这可以简单的理解为是state的异步
MySetState.jsx
import React, { Component } from 'react'
export default class MySetState extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.clickHandle = this.clickHandle.bind(this)
}
clickHandle() {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
render() {
return (
<div>
<h3>State异步</h3>
<button onClick={this.clickHandle}>changeCount</button>
<p>{this.state.count}</p>
</div>
)
}
}
那我们怎么把它变成同步的呢
import React, { Component } from 'react'
export default class MySetState extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.clickHandle = this.clickHandle.bind(this)
}
// clickHandle() {
// this.setState({
// count: this.state.count + 1
// })
// console.log(this.state.count)
// }
async clickHandle() {
await this.setStateAsync({
count: this.state.count + 1
})
console.log(this.state.count)
}
setStateAsync(state){
return new Promise((resolve)=>{
this.setState(state,resolve)
})
}
render() {
return (
<div>
<h3>State异步</h3>
<button onClick={this.clickHandle}>changeCount</button>
<p>{this.state.count}</p>
</div>
)
}
}
从上面的代码中,我们可以知道,通过Promise,async await语法糖可以让state中代码变成同步的
生命周期函数(钩子函数)
挂在时: constructor:初始化(初始化状态,state) static getDerivedStateFromProps():虚拟DOM之后,实际DOM渲染之前 render:创建虚拟DOM,进行Diff算法,更新DOM树都是再次进行的 componentDidMount:组件渲染完成 更新时: static getDerivedStateFromProps():虚拟DOM之后,实际DOM渲染之前 shouldComponentUpdate:返回值为Boolean类型,返回true,则允许DOM更新,返回false,则不允许DOM更新(优化) render:创建虚拟DOM,进行Diff算法,更新DOM树都是再次进行的 getSnapshotBeforeUpdate(prevProps, prevState):更新前触发的钩子函数 componentDidUpdate:组件更新完成 卸载时: componentWillUnMount:组件即将卸载
LifeCycle.jsx
import React, { Component } from 'react'
export default class LifeCycle extends Component {
constructor(props){
super(props)
console.log("构造函数:constructor");
console.log(props.title);
this.state = {
count:0
}
}
static getDerivedStateFromProps(props, state){
console.log(props,state);//点了一下后的 {title: 'LifeCycle-fromParent测试参数'} {count: 1}
console.log("渲染之前:getDerivedStateFromProps");
return 10;
}
componentDidMount(){
console.log("渲染完成:componentDidMount");
}
shouldComponentUpdate(nextProps, nextState){
/**
* this.props === nextProps;
* this.state === nextState;
*/
if(nextProps.count === this.state.count){
return false;
}
console.log("是否允许渲染:shouldComponentUpdate");
return true;
}
clickHandle = () =>{
this.setState({
count:this.state.count+1
})
}
getSnapshotBeforeUpdate(prevProps, prevState){
console.log(prevState);//点了一下后 这是之前第一次的值{count: 0}
console.log("更新前:getSnapshotBeforeUpdate");
return prevProps.title;//LifeCycle-fromParent测试参数
}
componentDidUpdate(prevProps, prevState, snapshot){
console.log(snapshot);//LifeCycle-fromParent测试参数
console.log("更新后:componentDidUpdate");
}
componentWillUnmount(){
console.log("组件卸载:componentWillUnmount");
}
clickHandle=()=>{
this.setState({
count:this.state.count + 1
})
}
render() {
console.log("渲染中ing:render");
return (
<div>
<h3>生命周期函数</h3>
<button onClick={this.clickHandle}>增加</button>
<p>{this.state.count}</p>
</div>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import LifeCycle from './LifeCycle'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
<LifeCycle title="LifeCycle-fromParent测试参数"/>
</div>
);
虚拟DOM:DOM渲染速度加速
表单之受控组件
MyForm.jsx
import React, { Component } from 'react'
export default class MyForm extends Component {
constructor(props){
super(props)
this.state = {
username:""
}
}
changeHandle=(event)=>{
this.setState({
username:event.target.value
})
}
getChangedataHandle=()=>{
console.log(this.state.username)
}
render() {
return (
<div>
<h3>受控组件</h3>
<input type='text' value={this.state.username} onChange={this.changeHandle}/>
<button onClick={this.getChangedataHandle}>get-change-data</button>
</div>
)
}
}
从上面的代码中,我们可以看出input里的vaule值是受到state管理的,但是如果有上百个input,我们就要为每其都绑定一个onChange事件,很麻烦,如何解决呢?
我们可不可以通过只绑定一个事件,来实现呢?如果绑定了同一个onChange事件,只要输入框输入一个值其他的input也相应的改变了,好像不可以......
此时,ES6站出来给我们解决了这个问题,Please See The Coding
MyForm3.jsx
import React, { Component } from 'react'
export default class MyForm3 extends Component {
constructor(props){
super(props)
this.state ={
username:"",
password:"",
email:""
}
}
changeHandle=(event)=>{
this.setState({
[event.target.name] : event.target.value
})
}
getDataHandle=()=>{
console.log(this.state)
}
render() {
return (
<div>
<input type="text" name="username" onChange={this.changeHandle}/>
<input type="text" name="password" onChange={this.changeHandle}/>
<input type="text" name="email" onChange={this.changeHandle}/>
<button onClick={this.getDataHandle}>get data</button>
</div>
)
}
}
其实就是加了个name属性,关键代码:[event.target.name] : event.target.value
表单之非受控组件
MyForm2.jsx
import React, { Component } from 'react'
export default class MyForm2 extends Component {
constructor(props){
super(props)
this.username = React.createRef()
this.password = React.createRef()
}
getDataHandle=()=>{
// console.log(this.username)//{current: input}
// console.log(this.username.current) //<input type="text">
console.log(this.username.current.value)
console.log(this.password.current.value)
}
render() {
return (
<div>
<h3>非受控组件</h3>
<input type="text" ref={this.username}/>
<input type="text" ref={this.password}/>
<button onClick={this.getDataHandle}>get data</button>
</div>
)
}
}
我们通过React.createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref
实际上就是获取元素节点
class MyComponent extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } render() { return <input type="text" ref={this.inputRef} />; } componentDidMount() { this.inputRef.current.focus(); } }
组合
Composition.jsx
import React, { Component } from 'react'
export default class Composition extends Component {
render() {
return (
<div>
<h3>组合</h3>
<div>
{this.props.children}
</div>
</div>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Composition from './Composition'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
{/*
Vue:插槽
React:组合
*/}
<Composition>
<p>我是组合内容-from index.js</p>
<p>哈哈哈-from index.js</p>
</Composition>
</div>
);
状态提升-组件之间的数据传递
props: 父传子
Child.jsx
import React, { Component } from 'react'
export default class Child extends Component {
constructor(props){
super(props)
this.state ={
msg:'from_child_data'
}
}
clickHandle=()=>{//必须使用箭头函数
this.props.onEvent(this.state.msg)
}
render() {
return (
<div>
<h3>Child</h3>
<p>from parent title==>{this.props.title}</p>
<button onClick={this.clickHandle}>return_callback_to_parent</button>
</div>
)
}
}
Parent.jsx
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
msg: ""
}
}
callbackHandle = (fromChild_data) => {
this.setState({
msg: fromChild_data
})
}
render() {
return (
<div>
<h3>Parent</h3>
<Child title='Parent-title' onEvent={this.callbackHandle} />
<p>from child data==>{this.state.msg}</p>
</div>
)
}
}
状态提升
Calculator.jsx
import React, { Component } from 'react'
import TemperatureInput from './TemperatureInput'
import BoilingVerdict from './BoilingVerdict'
/**
* 接受一个华氏度参数,转换为摄氏度
*/
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
/**
* 接受一个摄氏度,转换为华氏度
*/
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
// 温度转换
function tryConvert(temperature, convert) {
// 字符串 -> 数字类型
const input = parseFloat(temperature);
// isNaN:ES6新特性
if (Number.isNaN(input)) {
return ""
}
const output = convert(input);
// Math.round:获得一个整数(四舍五入)
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
export default class Calculator extends Component {
constructor() {
super();
this.state = {
temperature: "",
scale: "c"
}
}
handleCelsiusChange = (temperature) => {
this.setState({
scale: "c", // 摄氏度
temperature
})
}
handleFahrenheitChange = (temperature) => {
this.setState({
scale: "f", // 华氏度
temperature
})
}
render() {
const temperature = this.state.temperature
const scale = this.state.scale
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature
return (
<div>
<TemperatureInput
scale="Celsius"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange}
/>
<TemperatureInput
scale="Fahrenheit"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange}
/>
<BoilingVerdict
celsius={parseFloat(celsius)}
/>
</div>
)
}
}
TemperatureInput.jsx
import React, { Component } from 'react'
export default class TemperatureInput extends Component {
handleChange = (e) =>{
this.props.onTemperatureChange(e.target.value)
}
render() {
// scale:华氏度 摄氏度
const scale = this.props.scale
const temperature = this.props.temperature;
return (
<fieldset>
<legend>Enter temperatur in { scale }</legend>
<input type="text" value={ temperature } onChange={ this.handleChange }/>
</fieldset>
)
}
}
BoilingVerdict.jsx
import React, { Component } from 'react'
export default class BoilingVerdict extends Component {
render() {
// 接受一个参数:温度度数
const celsius = this.props.celsius
return (
<div>
{
celsius >= 100 ? '达到了水的沸点' : "未达到水的沸点"
}
</div>
)
}
}
React基础知识已经完结,接下来继续写React高级部分,持续更新中......