React基础学习
在安装react脚手架之前,要确保本地电脑中,已经安装过来nodejs的环境。
安装官方脚手架
npm i -g create-react-app
初始化
create-react-app 项目名称
扩展
在执行初始化命令的时候,会发现终端中会下载 react,react-dom,react-scripts三个包。
react包就是处理react项目的逻辑,react-dom包将虚拟dom渲染成真实dom,react-scripts包是react的脚本包,包含了许多编写react时所需要的各种包和配置环境。
如果碰到安装太慢,就先停止初始化,然后执行以下命令:
npm config set registry http://registry.npmmirror.com 然后再重新初始化。
然后依次执行以下命令(终端中也会提示)
# 第一步:进入到react项目目录中
cd 项目名称
执行完命令,在工程目录下会生成以下目录结构
-项目名称
-node_modules # 项目包依赖的文件
-public # 静态资源
-src # 源码,在开发过程中最常用的文件
-.gitignore
-package.json # 配置文件
-README.md
-yarn.lock # 入口文件
# 第二步:启动项目
npm start
由于刚上手react框架,刚生成的项目目录中含有许多陌生的文件,但是前期的基础学习用不到这些文件,所以建议将src目录中的以下文件删除,让整个项目更干净,更有利于学习react。
# src目录下要删除的文件
App.css
App.js
App.test.js
index.css
index.js
serviceWorker.js
然后在src目录下新建一个index.js的空文件,然后往index.js文件中写入以下内容,作为项目的入口文件。
// 项目的入口文件
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<div>Hello World!</div>, document.getElementById("root"));
然后在浏览器中打开http://localhost:3000 地址,看到 Hello World! 的字样,则说明项目搭建成功。
JSX介绍
jsx 等价于 javaScript + Xml,jsx就是一个对象,被称之为虚拟DOM元素。
<h2>hello,react<h2>
这个就是一个jsx对象,也就是虚拟DOM对象
import React from "react"; import ReactDOM from "react-dom"; // 此时,这个 <h2>hello,react<h2> 就是一个jsx对象 const ele = <h2>hello,react<h2>; console.log(ele); // 将ele处插入到id为root的标签中 ReactDOM.render(ele,document.querySelector('#root'));
模板语法渲染
插入动态的元素
// 使用{}就可以插入动态的元素,这个元素可以是任意内容 const ele = <h2>hello,{'react'}<h2>;
将函数插入到ReactDOM.render()中
import React from "react"; import ReactDOM from "react-dom"; function getGeeting(user){ if(user){ return <h1>hello,{user}</h1>; } return <h1>hello,react</h1>; } // 这里如果直接插入jsx对象,必须要用div标签包裹 ReactDOM.render(<div>getGeeting('用户名')</div>,document.querySelector('#root'));
元素渲染
元素是构成React应用的最小砖块,比如:
const ele = <h1>hello,world</h1>
与浏览器的DOM元素不同,React元素创建开销极小的普通对象,React DOM会负责更新DOM来与React元素保持一致。
ReactDOM.render()
其实就是在渲染DOM节点。
也就是说,react只更新它需要更新的部分。
案例
下面这段代码案例,实现的效果就是时间在变化,也就是,在整个页面中,只有h2元素在变动,其他元素都没有做任何改变。
import React from "react"; import ReactDOM from "react-dom"; function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>{new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render(element, document.querySelector('#root')); } setInterval(tick, 1000);
循环语句
循环语句,可以用 map((item,index)=>{})
import React from "react"; import ReactDOM from "react-dom"; const arr = ['1','2','3'] const ulEle = ( <ul> {arr.map((item,index)=>{ // 循环绑定的jsx元素,必须要有key属性,来区分不同的元素,否则会报错 return <li key={item}>{item}</li> })} </ul> ) ReactDOM.render(elEle,document.querySelector('#root'));
过滤语句
import React from "react"; import ReactDOM from "react-dom"; const goods = [ {id:1.price:100,title:'小米6手机'}, {id:2.price:200,title:'小米7手机'}, {id:3.price:1100,title:'小米8手机'}, {id:4.price:2000,title:'小米9手机'}, ] // 过滤操作:取出goods中price大于1000的数据 // 使用三元运算符来实现过滤效果 const filterEle = ( <ul> { goods.map((good,index)=>{ // 使用三目运算符 实现 过滤功能 return (good.price >1000 ? <li key={good.id}>{good.title}</li> : null) }) } </ul> ) ReactDOM.render(filterEle,document.querySelector('#root'));
组件的创建
react核心的思想就是组件化开发,其实就是玩JavaScript。
react有两种创建组件的方式,一种是函数声明,一个是类声明。
函数声明
import React from "react"; import ReactDOM from "react-dom"; // 第一步:用函数定义组件 // function 组件名(){} // 注意:组件名的首字母必须要大写,否则报错 function Weclome(props){ // (固定)props 接受 name='Welcome' // 如果组件没有传值,props就为空 return <h2>hello,{props.name}</h2> } // 第二步:使用组件 <组件名 /> ReactDOM.render(<Welcome name='Welcome' />,document.querySelector('#root'));
类声明
类声明的组件,使用的更多,类声明的组件声明周期。
注意:
1.React.Component是一个基类,使用类声明的组件,必须继承整个基类。
2.在类中,必须有render函数。
3.在render函数中,需要return 一个 jsx 元素。
4.组件的名称要以大写字母开头
// 必须继承React.Componet class App extends React.Component{ // 必须使用render()函数 能将虚拟DOM渲染成真实DOM render(){ // 获取组件中传来的值: this.props.值 // 它会将jsx所接收的属性转换为单个对象传递到组件,整个对象我们称为为props return <h2>App,{this.props.name}</h2> } } ReactDOM.render(<App name="你好" />,document.querySelector('#root'));
当然了,函数式组件的声明,也可以放在外部文件单独声明,然后到导入到index.js中。
比如,在src目录下,新建一个App.js文件,往里面写入以下代码(将上面的代码复制过来就好)
// App.js文件 import React,{Component} from 'react'; export default class App extends Component{ render(){ return <h2>App,{this.props.name}</h2> } } ReactDOM.render(<App name="你好" />,document.querySelector('#root'));
然后再回到index.js文件中,引入App.js组件
import React from 'react'; import ReactDOM from 'react-dom'; // 引入 App.js文件 import App from './App'; ReactDOM.render(<App name="你好" />,document.querySelector('#root'));
扩展
在使用vscode编辑器的时候,会发现定义一个类声明的组件,写法很麻烦,这里推荐一个vscode的插件,叫 ES7+ React/Redux/React-Native snippets
,只需要在代码中,写rcc就可以自动生成以下代码
import React,{Component} from "react"; // 这里的App 取决于文件名 export default class App extends Component { render() { return ( <div>App</div> ) } }
可以说是非常方便。
组件的复用
1.组件的复用可以将多个组件进行整合,例如调用多次相同的组件。
2.结构非常复杂时需要将组件拆分成小组件
3.会存在父子关系的数据传递
// App.js文件 import React,{Component} from "react"; class MyBtn extends Component{ render(){ return ( <button>{this.props.title}</button> ) } } export default class App extends Component{ render(){ return( <div> <h2>hello</h2> // 调用MyBtn类组件 复用组件 <MyBtn> title="提交"</MyBtn> <MyBtn> title="删除"</MyBtn> </div> ) } }
组件的拆分
将一个组件细分成更多的小组件。
扩展
在html中类名是class,但是在react中类名是className
// App.js文件 import React,{Component} from "react"; // 按钮组件 class MyBtn extends Component{ render(){ return ( <button>{this.props.title}</button> ) } } // 评论中 用户信息 中 头像组件 class Avatar extends Component{ render(){ return( <img src={this.props.avatarUrl} alt="" /> ) } } // 评论中 用户信息组件 class UserInfo extends Component{ render(){ return( <div className='userinfo'> <Avatar avatarUrl={this.props.avatarUrl}></Avatar> <div className="username"> {this.props.name} </div> </div> ) } } // 评论组件 class Comment extends Component{ render(){ return( //在html中类名是class,但是在react中类名是className <div className='comment'> {/*用户信息*/} <UserInfo {...this.props.user}></UserInfo> {/*用户评论内容*/} <div className='comment-content'> 评论内容:{this.props.user.content} </div> {/*用户评论时间*/} <div className='comment-date'> 发布时间:{this.props.user.date} </div> </div> ) } } // App 主组件 export default class App extends Component{ constructor(props){ super(props); // 遵循单向数据流 // 模拟后端传数据 this.user = { avatarUrl:'https://img2.baidu.com/it/u=1316245042,2395535024&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500', name:'用户', content:'这是我的react组件', date:new Date().toLocaleTimeString() } } render(){ return( // 调用MyBtn类组件 复用组件 <div> <h2>hello</h2> <MyBtn title="提交"></MyBtn> <MyBtn title="删除"></MyBtn> <MyBtn title="修改"></MyBtn> <MyBtn title="添加"></MyBtn> <Comment user={this.user}></Comment> </div> ) } }
css样式
想要给组件设置样式,就要在src目录下单独再创建一个css文件,然后再组件中导入该样式文件。
/* App.css */
/* App.js */ import './App.css'
子组件向父组件传递数据
子组件想要向父组件传递数据,就要在子组件和父组件中分别定制一个方法,在方法中来实现数据的传递。
import React,{Component} from "react"; // 子组件 class Child extends Component{ handleClick = () => { // 调用父组件传递过来的方法 // 将值通过props传递给父组件 this.props.add('子组件中的值'); } render(){ return( <div> <div>Child</div> <button onClick={this.handleClick}>子传父</button> </div> ) } } // 父组件 export default class Demo extends Component{ add(val){ // 获取子组件传递来的值 alert(val) console.log(val) } render(){ return ( <div> <h2>Parent</h2> <hr/> <Child add={this.add}></Child> </div> ) } }
注意:组件之间,想要修改值,不能直接修改。
State状态
State属性
组件内部以及组件之间的数据,不能直接的修改,要通过用State的方式定义数据和修改数据。
/* App.js */ import React, { Component } from 'react' export default class App extends Component { constructor(props){ super(props); // 1.在 constructor函数种使用this.state来定义数据 this.state = { count:0 } } add =()=>{ // 2.修改状态的唯一方法是调用this.setState() this.setState({ count:this.state.count+1 }) } render() { console.log("渲染了") return ( <div> <h2>{this.state.count}</h2> <button onClick={this.add}>+1</button> </div> ) } }
State状态的this指向
在React中,用State修饰数据,要注意this指向的问题。如果处理不好this指向的问题,那就会得不到数据,也就修改不了数据。
修改this指向的方法
第一种方式
在 constructor方法中,将this绑定给add方法
// 在 constructor方法中 constructor(props){ super(props); this.state = { count:0 } // 第一种方式:改变this的指向 this.add = this.add.bind(this) } add(){ // 除了constructor之外的其他地方,修改状态的唯一方法是调用this.setState() this.setState({ count:this.state.count+1 }) } render() { console.log("渲染了") return ( <div> <h2>{this.state.count}</h2> <button onClick={this.add}>+1</button> </div> ) }
第二种方式
给修改数据的方法(add) 使用箭头函数
// 在 constructor方法中 constructor(props){ super(props); this.state = { count:0 } } add = () => { // 除了constructor之外的其他地方,修改状态的唯一方法是调用this.setState() this.setState({ count:this.state.count+1 }) } render() { console.log("渲染了") return ( <div> <h2>{this.state.count}</h2> <button onClick={this.add}>+1</button> </div> ) }
第三种方式
在标签中使用bind方式绑定
<button onClick={this.add.bind(this)}>+1</button>
第四种方式
推荐使用,因为传值方便
在标签中,使用箭头函数
add(e){} render() { return( <button onClick={(e)=>this.add(e)}>+1</button> ) }
完整代码
import React, { Component } from 'react' export default class App extends Component { constructor(props){ super(props); this.state = { count:0 } // 第一种方式:改变this的指向 this.add = this.add.bind(this) } // 第三种方式:使用箭头函数 add =()=>{ // 除了constructor之外的其他地方,修改状态的唯一方法是调用this.setState() this.setState({ count:this.state.count+1 }) } render() { console.log("渲染了") return ( // 第二种方式改变this指向 // <button onClick={this.add.bind(this)}>+1</button> // 第四种方式: // 推荐使用,因为传值方便 // <button onClick={()=>this.add()}>+1</button> <div> <h2>{this.state.count}</h2> <button onClick={this.add}>+1</button> </div> ) } }
setState方法的使用
上面用的setState({})
对象形式 会有一个弊端,这个弊端是因为react内部是异步执行代码,所以这个弊端就会造成一个现象:
constructor(props){ super(props); this.state = { count:0 } } add = () => { this.setState({ // 此时count已经+1,所以当第一次点击的时候,count的结果是2 count:this.state.count+1 }) // 虽然count的结果已经改成2了,但是这里打印出的结果仍然是1 //这就是异步造成的结果,上面代码和下面代码同时进行,同时修改值,同时输出打印,所以值是修改了,但是打印的还是修改前的值 console.log(this.state.count) }
所以为了解决上述的弊端问题,下面就引入了
// prevState 表示之前的状态 // prevProps 表示之前的属性 this.setState((prevState,prevProps)=>({ // 修改值的操作 }),()=>{ // 处理最新的状态 })
函数方式,来解决。
代码案例
// 在 constructor方法中 constructor(props){ super(props); this.state = { count:0 } } add = () => { // 使用setState()函数 this.setState((prevState,prevProps)=>({ // 修改count的值 count:prevState.count + 1 }),()=>{ // 处理最新的状态 console.log(this.state.count); }) }
其实,针对于这个计数器的案例来说,如果直接使用this.setState({})
对象的形式来修改count值,也可以用 count:this.state.count+=1
的形式来实现页面显示的数据 和 打印输出 的数据相同。
React生命周期
案例
在src目录下新建一个LifyCycle.js文件
// LifyCycle.js import React,{ Component } from 'react' // 子组件 export class SubCount extends Component{ componentWillReceiveProps(newProps){ console.log('子组件将要接收新属性',newProps); } render(){ return( <div> </div> ) } } export default class LifeCycle extends Component{ static defaultProps = { // 1.加载默认的属性 name:'小马哥', age:18 } constructor(props){ super(props); console.log('1.初始化 加载默认的状态'); this.state = { count:0 } } componentWillMount(){ console.log('2.父组件将要被挂载'); } componentDidMount(){ // 在当前的这个方法中,发起ajax请求,获取数据,数据驱动视图 console.log('4.父组件挂载完成'); } shouldComponentUpdate(nextProps,nextState){ // 性能的优化点 重要 // 绝对组件是否要重新渲染 console.log('5.组件是否要更新'); if(nextState.count % 2 === 0){ return false; }else{ return true; } } componentWillUpdate(){ console.log('7.组件将要更新'); } componentDidUpdate(){ console.log('8.组件已经更新完成'); } componentWillUnmount(){ console.log('组件卸载完成'); } handleClick=() => { this.setState((preveState,preveProps)=>({ count:preveState.count + 1 }),()=>{ console.log(this.state.count); }) } render(){ console.log('3.父组件(render)了'); return ( <div> <h2>当前的值:{this.state.count}</h2> <button onClick={this.handleClick}>+1</button> <SubCount count={this.state.count}></SubCount> </div> ) } }
受控组件
受控组件就是 受状态控制的组件,需要与状态进行相应的绑定
受控组件必须要有一个onChange事件,否则不能使用
受控组件可以赋予默认值(实际上就是设置初始状态)
案例
新建一个ControlInput.js文件做案例
// ControlInput.js import React, { Component } from 'react' export default class ControlInput extends Component { constructor(props){ super(props); this.state = { val:'', // 设置初始值 data:[] } } handleChange = (e) => { // 获取input输入框的值 let val = e.target.value; this.setState({ val }) // 打印input中的值 console.log(this.state.val); } // 按钮点击事件 handleClick = () => { if(this.state.val){ let newArr = [...this.state.data]; newArr.push(this.state.val); this.setState({ data:newArr }) // 添加完毕,清空输入框 this.setState({ val:'' }) } } render() { return ( <div> <input type="text" value={this.state.val} onChange={this.handleChange}/> <button onClick={this.handleClick}>添加数据</button> <ul> { this.state.data && this.state.data.map((item,i)=>{ return ( <li key={i}>{item}</li> ) }) } </ul> </div> ) } }
记住在index.js中导入并且使用该文件
// index.js import ControlInput from "./ControlInput"; // 加载组件 ReactDOM.render(<ControlInput/>,document.querySelector('#root'));
非受控组件
非受控组件就是不受状态的控制,有点类似于vue中的双向数据绑定
实现过程
1.通过ref标记一个元素,然后可以通过this.refs.xxx 来获取这个元素
2.通过onChange事件监听到value的变化,获取到这个数据
3.然后通过操作DOM将数据放到需要的地方
案例
新建一个NoControlInput.js文件做案例
// NoControlInput.js import React,{Component} from "react"; // 非受控组件 export default class NoControlInput extends Component{ constructor(props){ super(props); this.state = { val:'' // 设置input的初始值为空 }s } // 当input组件发生变化时,触发 handleChange = (e) => { // 通过refs.xxx.value 获取input的值 let val = this.refs.a.value; this.setState({ val }) } render(){ return( <div> <input type="text" onChange={this.handleChange} ref="a" /> <h2>{this.state.val}</h2> </div> ) } }
记住在index.js中导入并且使用该文件
// index.js import NoControlInput from "./NoControlInput"; // 加载组件 ReactDOM.render(<NoControlInput/>,document.querySelector('#root'));
表单的使用
案例
新建一个FormSimple.js文件做案例
import React,{Component} from "react"; export default class FormSimple extends Component{ constructor(props){ super(props); this.state = { usename:'', password:'', selectedArr:[] } } handleUserName = (e) => { this.setState({ username:e.target.value }) } handlePassword = (e) => { this.setState({ password:e.target.value }) } handleSelectedChange = (e) => { let newArr = [...this.state.selectedArr]; // 将选中的数据添加到数组中 newArr.push(e.target.value); this.setState({ selectedArr:newArr }) } // 表单一旦提交,就触发该方法 handleSubmit = (e)=> { // 阻止默认事件 e.preventDefault(); if(this.state.username && this.state.password && this.state.selectedArr && this.state.selectedArr.length>0){ let arr = this.state.selectedArr.map(n=>(`${n}`)) // 模拟 发送ajax请求 alert(`当前用户名:${this.state.username},密码:${this.state.password},我的爱好:${arr}`) } } render(){ return( <form onSubmit={this.handleSubmit}> <p className="username"> <label htmlFor="name">用户名:</label> <input type="text" value={this.state.username} onChange={this.handleUserName} id="name" /> </p> <p className="password"> <label htmlFor="pwd">密码:</label> <input type="text" value={this.state.password} onChange={this.handlePassword} id="pwd" /> </p> 我的爱好 <select multiple value={this.state.selectedArr} onChange={this.handleSelectedChange}> <option value="smoking">抽烟</option> <option value="tangtou">烫头</option> <option value="drink">喝酒</option> </select> <br/> <input type="submit" value="登录"/> </form> ) } }
扩展
在html中,label标签使用的是for属性,但是在react中,使用的是htmlFor属性
记住在index.js中导入并且使用该文件
// index.js import FormSimple from './FormSimple'; // 加载组件 ReactDOM.render(<FormSimple />,document.querySelector('#root'));
React组件化
前期准备
首先新建一个react组件化文件夹,然后创建 02_component 的react工程项目。
create-react-app 02_component # 创建react工程
yarn安装
npm install -g yarn
Ant-Design的使用
Ant-Design的环境依赖安装
在终端中,进入到项目的目录当中,下载依赖包
cnpm i antd -S # 或者 yarn add antd
注意:cnpm要在安装了淘宝镜像源才能使用。
yarn要安装了之后才能使用。
全局导入组件(不推荐)
在App.js文件中导入ant-design
// import 组件 from 'antd/es/组件' // 比如说导入button import Button from 'antd/es/button'; import 'antd/dist/reset.css';
使用Button组件
function App(){ return ( <div> <Button type="primary">登录<Button> </div> ) }
由于全局导入的方式太过于消耗内存资源,所以建议使用高级配置。
按需导入组件(推荐)
安装依赖
cnpm i react-app-rewired customize-cra babel-plugin-import
修改配置文件
1.修改package.json文件
// 将"scripts"对象中的 react-scripts全部改为react-app-rewired // 大概在14至18行
2.在项目的根目录下新建一个config-overrides.js 文件,必须叫这个名字。
const {override,fixBabelImports} = require('customize-cra'); module.exports = override( fixBabelImports('import',{ libraryName:'antd', libraryDirectory:'es', style:'css' }) )
3.在App.js文件中,进行局部导入
import { Button } from "antd";
4.修改配置后,重启服务
聪明式组件和傻瓜式组件
聪明组件又称为容器组件,负责数据获取
傻瓜组件又称为展示组件,负责根据props显示信息内容
优势:
1.逻辑和内容展示分离
2.重复性高
3.复用性高
4.易于测试
案例
在/react组件化/02_component/src 目录下创建components文件夹,然后再在该文件夹下新建一个评论列表(ComponentList.js)文件。
// CommentList.js import React,{ Component } from 'react' // 此时Comment 就充当傻瓜式组件 function Comment(props){ const {id,content,author} = props.comments; return ( <div> <p>{id}</p> <p>{author}</p> <p>{content}</p> </div> ) } // 此时 Component 就充当聪明式组件 export default class CommentList extends Component{ constructor(props){ super(props); this.state = { comments:[] } } componentDidMount(){ setTimeout(() => { this.setState({ comments:[ { id:1, author:'facebook', content:'react非常好' }, { id:2, author:'尤雨溪', content:'Vue更好' } ] }) },1000); } render(){ return( <div> { this.state.comment.map((item,i)=>{ return ( <Comment key={item.id} comment={item} /> ) }) } </div> ) } }
最后不要忘记在App.js文件中引入上面的组件
// App.js import ComponentList from './components/CommentList' function App(){ return ( <div className="App"> <ComponentList></ComponentList> </div> ) }