React的基础使用及开发
React官网
安装create-react-app脚组架
优点:无需配置webpack,babel(把es6,7,8,9,…转换为es5的工具)
卸载/安装
npm uninstall -g create-react-app
npm install -g create-react-app
查看版本号
npx create-react-app --version
npx:npm 5.0+提供的一个命令;它可以执行安装依赖包,npx本身不需要单独安装
创建项目并启动
create-react-app xxx
或:npx create-react-app xxx
主要安装了react, react-dom, and react-scripts
主要安装: react.js, react-dom.js, react-scripts.js
cd xxx
npm start
打包npm run bulid
项目依赖与开发依赖
项目依赖:npm i --save xxx
开发依赖:npm i --save-dev xxx; npm i -D xxx
开发依赖在项目打包时不会打包到项目中。
项目目录
public/ 静态资源的目录。
-index.html
-favicon.ico 网站小图标
-logo192.png ,打包成APP的小图标。
-manifest.json 打包成APP的配置。
-robots.txt 谷歌搜索引擎用的一个配置文件。
src/ 开发的源代码存放的位置。 里面的所有文件,webpack都会进行打包处理。
index.js 项目的入口文件
了解ES6中的类的语法
// 1。类名,首字母要大写
class Person {
// 构造器中的属性,会做为实例化对象的直接属性
constructor(name, age) {
this.name = name;
this.age = age;
}
// 类中的方法,会在实例化对象的原型对象上显示;
// say
say(text) {
console.log(this.name + "说:" + text);
}
// write()
//不要要类中,写方法时,加function这个关键字。
write(text) {
console.log(this.name + "写:" + text);
}
}
// 2 子类继承父类
class Student extends Person {
constructor(name, age, className) {
// 只要使用了extends关键字,就必须要写super()
super(name, age); //通过super(参数列表)方法,去实现继承父类的属性;
this.className = className;
}
study() {
}
}
//实例化对象:
let s = new Student("张三", 18, "一班")
console.log(s);
map/filter/every/find/some
JS中的Array.prototype.map(callback):创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值
JSX的基本使用
创建文件
创建一个login页:
index.js中引入
Fragment
审查元素多一层div:
使用Fragment:
或者使用空标签<></>:
变量、表达式
{this.state.xxx}
import React, { Component,Fragment } from 'react'
export default class About extends Component {
render() {
let name = 'About'
let user = {
name:'zz',
age:22
}
let getName = function () {
return 'll'
}
let list = ['a','b','c','d']
let ahref = 'http://www.baidu.com'
return (
<Fragment>
<h1>{name}</h1>
<p>{user.name}</p>
<p>{getName()}</p>
<p>{list.join('$$')}</p>
<p>{user.age<19?'未成年':'成年'}</p>
<a href={ahref}>超链接</a>
</Fragment>
)
}
}
图片的四种处理:
//静态加载图片
import log from "./assets/1.jpg";
<img src={log} />
//动态加载图片,本地src目录下的图片,不能直接使用字符串来表示源。
<img src={require("./assets/1.jpg").default} />
//使用的是public目录中的图片。
<img src="logo192.png" />
//网络资源 :
<img src="https://xxx/react.png" />
class style
class属性,要写成className。不然就是写成了ES6的class关键字。
render() {
const styledata = {color:'yellow'}
return (
<div>
<p className='pstyle' style={styledata}>hahah</p>
<p className='this.state.className1' style={{backgroundColor:'red'}}>hahah</p>
</div>
)
}
原生的html
__html:“html代码”
dangerouslySetInnerHTML={doHtmlData} 引入
render() {
const htmlData = "<a href='#'>html a</a>"
const doHtmlData = {
__html:htmlData
}
return (
<div>
<p dangerouslySetInnerHTML={doHtmlData}></p> //渲染hml
<p>{htmlData}</p> //无法渲染
</div>
)
}
htmlFor
<label for="">
<input id=“”/>
</label>
//写成:
<label htmlFor="">
<input id=“”/>
</label>
条件判断
if else,三元运算符,&& 和 ||
render() {
let user = {
name:'zz',
age:22
}
let str;
if(user.name<18){
str = <p>未成年</p>
}else{
str = <p>成年</p>
}
return (
<Fragment>
{str}
</Fragment>
)
}
列表渲染
map:按照约定的规则,返回一个新的数组
import React, { Component,Fragment } from 'react'
export default class About extends Component {
render() {
let list = ['a','b','c','d']
let list2 = list.map((item,index)=>{
return (
<li key={index}>{item}</li>
)
})
let list3 = ()=>{
let newList = []
let index = 0
for(let key in list){
newList.push(
<li key={index++}>{list[key]}</li>
)
}
return newList
}
return (
<Fragment>
<ul>
{list.map((item,index)=>{
return <li key={index}>{item}</li>
})}
</ul>
<ul>
{list2}
</ul>
<ul>
{list3()}
</ul>
</Fragment>
)
}
}
react 组件
概念
组件主要是用来高耦合,低内聚。
高耦合:把逻辑紧密的内容,都放在一个组件 中;
低内聚:不同组件的依赖关系要尽量弱化,每个组件尽量的独立出来;
传统组件的特点:进行简单的封装,简单的生命周期的呈现,有数据的流动。缺点是不能把组件中的结构和样式以及行为结合在一起,维护困难。
组件的主要内容:构建方式,组件有哪些属性,生命周期的应用。
组件化:就是你的程序使用组件来开发并完成的,这就是组件化的应用。
主要包括: state, props, 生命周期;
特点: 分离页面中的UI部分。让每一个组件尽量的细小,让其它开发者可以共享组件 。
组件是用来实现页面局部功能的代码的集合。是用来简化页面的复杂程序的,所以不要在一个组件中写太多的内容。
分类
有状态的组件:class组件,它有state属性
无状态的组件:函数式组件,它没有state属性; 它利用HOOKS技术来处理状态。
事件以及事件参数event
bind is
关于event参数
传递自定义参数
方法一
使用bind修改this指向
constructor(props){
super(props)
this.state = {
name:'曾曾'
}
//修改this的指向
this.clickChange = this.clickChange.bind(this)
}
//普通函数
clickChange(e){
this.setState({
name:'zzz'
})
}
render() {
return (
<div>
传递事件对象:<p onClick={(e)=>this.clickChange('123',100,e)}>
不传事件对象:<p onClick={this.clickChange}>
点我触发事件修改:{this.state.name}
</p>
</div>
)
}
方法二(不推荐)
下方写bind
方法二比较方法一缺点:每点击一次都会调用一次bind性能低,而方法一只调一次。
<p onClick={this.clickChange.bind(this,参数)}>
点我触发事件修改:{this.state.name}
</p>
方法三(推荐)
使用箭头函数
clickChange3 = (num,e) =>{
console.log(e) //SyntheticBaseEvent
this.setState({
name:'zzz'
})
}
-----------------------------------------------
传递事件对象: <p onClick={(e)=>this.clickChange3(99,e)}>
不传事件对象,默认函数接收第一个参数是e
<p onClick={this.clickChange3}>
点我触发事件修改:{this.state.name}
</p>
SyntheticBaseEvent 组合event 是react封装的,原生的event是MouseEvent
event.nativeEvent可以获取原生的event
所有的事件都挂在document上 e.nativeEvent.currentTarget 和 e.currentTarget不同
表单
受控组件
input、textarea、select、checkbox、radio
input的值和state中的值进行绑定,input的值受state控制。
实现的效果类似于:v-model的双向数据绑定。
constructor(props){
super(props)
this.state = {
val:'',
checked:true
}
this.changeVal = this.changeVal.bind(this)
}
changeVal(e){
this.setState({
val:e.target.value,
checked:!this.state.checked
})
}
render() {
return (
<div>
<p>{this.state.val}</p>
<div>
<input value={this.state.val} onChange={this.changeVal}></input>
</div>
<textarea value={this.state.val} onChange={this.changeVal}></textarea>
<div>
<select value={this.state.val} onChange={this.changeVal}>
<option value='beijin'>北京</option>
<option value='shanghai'>上海</option>
</select>
</div>
<div>
<input type='checkbox' checked={this.state.checked}></input>
<p>{this.state.checked.toString()}</p>
</div>
<input type='radio' checked={this.state.checked}></input>
</div>
)
}
组件的使用
props传递数据、函数、prop-types做类型检查
父子传参事件案例
//父
import React, { Component,Fragment } from 'react';
import Son from './son';
export default class About2 extends Component {
constructor(){
super()
this.state={
name:'zz',
pass:'123',
redStyle:{
backgroundColor:'black',
color:'red'
}
}
}
changeName = (newName,e)=>{
this.setState({
name:newName
})
console.log(e);
}
render() {
return (
<Fragment>
<p>姓名:{this.state.name}</p>
<button onClick={(e)=>this.changeName('ddd',e)}>点击更改姓名</button>
<hr />
<Son
text={this.state.name}
style={this.state.redStyle}
myClick={this.changeName}
/>
</Fragment>
)
}
}
子props接收
//子props接收
import React, { Component,Fragment } from 'react'
export default class Son extends Component {
render() {
return (
<Fragment>
<button type='button'
style={this.props.style}
onClick={(e)=>this.props.myClick('Son Click1',e)}
>
Son btn111 {this.props.text}
</button>
<button type='button'
style={this.props.style}
onClick={this.props.myClick.bind(this,'Son Click2')}
>
Son btn222 {this.props.text}
</button>
</Fragment>
)
}
}
todoList案例
过程及PropTypes类型检查
propTypes类型检查详细文档
父组件存放 Input 和 List 两个子组件以及总数据
List:父组件传递todolist给它循环渲染
Input:父组件传递写入的方法给它进行传参
安装开发依赖
npm i -D prop-types
案例:
import PropTypes from 'prop-types';
组件名.propTypes = {
props中的属性名: PropTypes.string
};
//如:
Button.propTypes = {
myType: PropTypes.string,
myClick: PropTypes.func,
myStyle: PropTypes.object,
title: PropTypes.string,
};
//defaultProps
//指定 props 的默认值:
Button.defaultProps = {
name: 'name没有传时默认显示的值'
};
//或者使用static
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
static propTypes = {
myType: PropTypes.string
};
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
父组件:
import React, { Component } from 'react'
import Input from '../component/Input'
import List from '../component/List'
export default class PropsDemo extends Component {
constructor(props){
super(props)
//数据提升
this.state = {
val:'',
todolist:[
{id:1,text:'aaaaa'},
{id:2,text:'b'},
{id:3,text:'cc'},
{id:4,text:'dddd'}
]
}
this.onaddText = this.onaddText.bind(this)
}
render() {
return (
<div>
{/* 父--》子函数 */}
<Input addText = {this.onaddText}></Input>
{/* //父->子渲染 //类型检查*/}
<List list={this.state.todolist}></List>
</div>
)
}
onaddText(data){
this.setState({
todolist:[...this.state.todolist,
{
id:this.state.todolist.length+1,
text:data
}
]
})
}
}
List组件:
const {list} = this.props //解构赋值再进行数据处理
import React, { Component } from 'react'
//类型检查
import PropTypes from 'prop-types'
export default class List extends Component {
render() {
const {list} = this.props
console.log(list);
return (
<div>
<ul>
{
list.map((item)=>{
return <li key = {item.id}>{item.text}</li>
})
}
</ul>
</div>
)
}
}
//props 类型检查
List.propTypes = {
//list是一个数组,数组里面每一项是一个对象
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
list:PropTypes.arrayOf(PropTypes.object).isRequired
}
Input 组件:
const {addText} = this.props //解构赋值
addText(this.state.data) //作为函数传参调用
import React, { Component } from 'react'
export default class Input extends Component {
constructor(props){
super(props)
this.state={
data:''
}
}
render() {
return (
<div>
<p>Data:{this.state.data}</p>
<input value={this.state.data} onChange={this.changeData}></input>
<button onClick={this.onSubmit}>添加</button>
</div>
)
}
changeData = (e) =>{
this.setState({
data:e.target.value
})
}
onSubmit = () =>{
//把父函数传过来给父函数传参
const {addText} = this.props
console.log(addText);
addText(this.state.data)
this.setState({
data:''
})
}
}
fetch
login() {
let userInfo = {
user: this.state.uIn,
pass: this.state.uPsIn
}
let that = this
var url = 'http://127.0.0.1:8001/login';
fetch(url,{
method:'POST',
body: JSON.stringify(userInfo),
mode: 'cors',
headers: {
'Content-Type': 'application/json',
}
})
.then((response)=>{
console.log(response);
if (response.status === 200) {
return response.json()
}
})
.then((res)=>{
console.log(res);
if (res.state == 1) {
alert('登录成功')
} else {
alert('登录失败')
}
that.setState({
uIn: '',
uPsIn: ''
})
})
.catch(error => console.error('Error:', error))
}
非受控组件 refs
转发refs,react中提供一个ref的数据;
refs表示当前组件的真正实例的引用 ,它会返回绑定的当前属性的元素。
当绑定ref后,就标识组件中内容的元素,方便我们查找属性。
早期只能在类组件中使用,函数式组件中不能使用。HOOKS可以解决函数式组件没有state的问题。
从性能上来讲,要减少refs的使用。
ref、defaultValue defaultChecked、手动操作DOM元素
方案一:
创建ref:this.inputVal = React.createRef()
调用ref: input ref={this.inputVal}
获取值:this.inputVal.current.value
方案二:
存储节点:this.属性名=""
创建ref:ref={(当前元素名)=>{this.属性名=当前元素名}}
获取值:this.refs名(属性名).value
应用场景:(手动操作DOM) 如:上传文件
constructor(props){
super(props)
this.state = {
val:'默认值'
}
this.inputVal = React.createRef()//创建ref
}
changeVal(e){
this.setState({
val:e.target.value,
checked:!this.state.checked
})
}
render() {
return (
<div>
<h1>非受控组件</h1>
<input defaultValue={this.state.val} ref={this.inputVal}></input>
<br></br>
<span>this.state.val:{this.state.val}</span>
<br></br>
<button onClick={this.getVal}>点击获取Input值</button>
</div>
)
}
getVal=()=>{
//this.inputVal.current获取ref节点
alert(this.inputVal.current.value)
}
案例二:受控组件非受控组件的实现:
import React, { Component,Fragment } from 'react'
import './login.css'
import log from '../../assets/my.jpg'
export default class Login extends Component {
constructor(){
super()
this.gpss = React.createRef()
this.text = ''
this.state = {
tel:''
}
}
getText(){
console.log(this.text.value);
console.log(this.gpss.current.value);
console.log(this.state.tel);
}
changeTel(e){
this.setState({
tel:e.target.value
},()=>{
console.log(this.state.tel);
})
}
render() {
return (
<Fragment>
<h1>Login</h1>
<img src={log} />
<img src={require("../../assets/my.jpg").default} />
<img src='logo192.png'/>
<div>
<div>
//v是input的DOM节点
用户名:<input type='text' ref={(v)=>{
this.text = v
}}/>
</div>
<div>
密码:<input type='password' ref={this.gpss}/>
</div>
<div>
双向绑定:<input type='tel' value={this.state.tel} onChange={(e)=>this.changeTel(e)}></input>
</div>
<button type='button' onClick={()=>{this.getText()}}>获取值</button>
</div>
</Fragment>
)
}
}
setState
不可变值
可能是异步更新
可能会被合并
不可变值
不能直接操作state值
addCount = () =>{
//不可变值:不能直接操作state值
//所以操作数组对象 不能使用push pop splice等操作
// 数组:追加:concat,[...list,xxx] 截取:slice 过滤filter
//对象: Object.assign({},this.state.obj1,{a:100})
//{...this.state.obj1,a:100}
this.setState({
count:this.state.count+1
})
}
可能是异步更新
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.addCount}>+1</button>
</div>
)
}
addCount = () =>{
this.setState({
count:this.state.count+1
})
console.log(this.state.count)//异步拿不到最新的值
}
结果:先打印再执行的count+1的操作
改为同步更新:
回调
addCount = () =>{
this.setState({
count:this.state.count+1
},()=>{
//setState的回调函数
console.log(this.state.count)
})
}
setTimeOut
setTimeout中setState是同步的:
addCount = () =>{
setTimeout(() => {
this.setState({
count:this.state.count+1
})
console.log(this.state.count);
}, 0);
}
自定义的DOM事件
addCount2= ()=>{
this.setState({
count:this.state.count+1
})
console.log(this.state.count);
}
componentDidMount(){
document.body.addEventListener('click',this.addCount2)
}
componentWillUnmount(){
document.body.removeEventListener('click',this.addCount2)
}
可能被合并
点击一次+1
addCount = () =>{
this.setState({
//count:0+1
count:this.state.count+1
})
this.setState({
//count:0+1
count:this.state.count+1
})
this.setState({
//count:0+1
count:this.state.count+1
})
}
函数不合并
点击一次+3
addCount = () =>{
this.setState((pre,props)=>{
return {
count:pre.count+1
}
})
this.setState((pre,props)=>{
return {
count:pre.count+1
}
})
this.setState((pre,props)=>{
return {
count:pre.count+1
}
})
}
组件的生命周期
1)挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount():挂载成功后,才会产生DOM节点对象;
2)更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
3)卸载
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount()
4)错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromError()
componentDidCatch()
Ant Design
npm install --save antd
路由
安装:
react-router-dom@5.2.0
+react-router@5.2.0
npm install react-router-dom react-router --save
通用组件
路由组件: BrowserRputer(history模式),需要服务器端的支持, HashRouter(hash模式);
路径匹配组件: Route ,Switch
导航组件: Link, NavLink(主要是增加激活样式);
Router路由的使用及Switch和Redirect
//index.js
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
//路由模式
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
//App.js
import {Route,Switch,Redirect} from 'react-router'
function App() {
return (
<div className="App">
<section>
{/* switch仅能有一个路由规则匹配url地址 */}
<Switch>
{/* exact精准匹配 */}
<Route path='/' exact component={Login} />
<Route path='*' component={Login} />
</Switch>
<Route path='/about2' component={About2} />
<Route path='/about3' component={About3} />
<Route path='*' component={Login} />
//或者使用重定向
<Redirect to='/login' from='*'></Redirect>
</section>
</div>
);
}
this.props获取路由对象
通过Route组件所匹配的规则而渲染出来的组件,可以直接通过this.props对象,获取到路由相关的三个对象:history, location, match
history:包括push(),replace(),可以实现编程式导航。即在JS代码中,实现页面的跳转。
eg:this.props.history.push(’/about’) 编程式导航实现跳转到about
location: 包括了浏览器地址栏中的的一个数据,如pathname, search;
match: params,其它的参数信息。
Link导航
import {Link} from 'react-router-dom'
<div>
<Link to="/about">About</Link>
<Link to="/about2">About2</Link>
<Link to="/about3">About3</Link>
</div>
Link控制子页面显示
通过this.props.location来接收参数和其它信息
import {Link,Route} from 'react-router-dom'
<div>
<Link to="/">Login</Link>
//target = "_blank" 在新标签页打开
<Link to="/about/about2" target = "_blank">About2</Link>
<Link to={{
pathname: "/about/about2",
search: "?sort=name",//只能是字符串形式,可以用不问号
hash: "#the-hash",
state: { id: 1 } //这是一个隐式传参。
}}>About2 Obj</Link>
<Link to="/about/about3">About3</Link>
</div>
<div>
子页面:
<Route path='/about/about3' component={About3} />
<Route path='/about/about2' component={About2} />
</div>
注:必须要先把search中的字符串,转换成JSON对象格式再做数据处理;
/**
* @desc 处理search中的参数,转换成JSON数组;
*/
myGetPrams() {
let search = this.props.location.search; //?uName=wqw&id=2"
search = search.slice(1, search.length); //uName=wqw&id=2
let arr1 = search.split("&"); // ["uName=wqw", "id=2"]
let arr2 = [];
arr1.forEach((item) => {
let obj = {};
let tempArr = [];
tempArr = item.split("="); // ["uName", "wqw"], ["id", "2"];
obj[tempArr[0]] = tempArr[1]; //{uName:wqw} {id:2}
arr2.push(obj);
});
console.log(arr2);
}
NavLink
在Link基础上,增加了激活样式。
import {Link,NavLink,Route} from 'react-router-dom'
<div>
<NavLink to="/about/about3/son"
activeClassName="activetest"
activeStyle ={{ fontWeight: "bold",
color: "red"}}
>To Son1</NavLink>
<NavLink to="/about/about3/son2"
activeClassName="activetest"
activeStyle ={{ fontWeight: "bold",
color: "red"}}
>To Son2</NavLink>
</div>
<div>
<Route path='/about/about3/son' component={Son}></Route>
<Route path='/about/about3/son2' component={Son2}></Route>
</div>
其他属性:
exact: 表示URL和Route中的规则完全一致时,才激活样式;
strict :匹配路径后面的"/",也就是是Route path="/events/",要考虑event后面的"/"在内;
isActive:是否被激活
isActive={(match, location) => {
return true;//一直为激活
}}
路由传参
-
在URL?KEY=VALUE,拼接参数字符串: 明码显示参数,参数多时地址的字符串会很长很长。获得方法:this.props.location.search
-
动态路由:id:明码显示参数,一般不会传对象,传一些简单的字符串或数字;获得方法:this.props.match.params
-
query属性:在Link中,to属性绑定一个对象,query属性的值,不会显示在地址栏中,但是刷新会丢失参数。获得方法:this.props.location.query
-
state属性:在Link中,to属性绑定一个对象,state属性的值,不会显示在地址栏中,但是刷新不会丢失参数。
获得方法:this.props.location.state
函数组件 路由传参
import { useHistory } from "react-router-dom";
//...
const history = useHistory()
// 将新入口放入历史堆栈
history.push({
pathname: '/the/path',
search: '?a=query',
// 一些不存在url参数上面的当前url的状态值
state: { the: 'state' }
})
withRouter
在没有通过Route跳转的组件中,使用withRouter;
如APP.js中,就没有通过路由来跳转,因此要引入withRouter
在render就可以获取到this.props
import { withRouter } from "react-router";
//...
export default withRouter(App);
在高阶组件中,当调用一个高阶组件时,返回一个新的子组件,这个子组件就不是能完Route来跳转的,因此不能正常获取到history,location,match三个对象。
return (
<>
widthRouter("<div>子组件</div>")
</>
)
函数组件
纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法
函数式组件是无状态组件; class组件是有状态组件;
函数式组件中,props直接写在()中,做函数的形参; (直接注入props),获取props属性时,不写this.props,而是直接写props.属性名。或名解构成一个新的对象: {title,location,histroy,match} = props;
import React from 'react'
export default function FunctionDom(props) {
const {list} = this.props
// const list = [
// {id:1,name:'001'},
// {id:2,name:'002'},
// {id:3,name:'003'}
// ]
return (
<div>
<ul>{list.map((item)=>{
return <li key={item.id}>{item.name}</li>
})}</ul>
</div>
)
}
路由
可以通过props.history或者props.match来进行路由操作或者获取路由数据,但是没有props或者以防万一取不到props则使用useHistory, useRouteMatch:
import { useHistory, useRouteMatch } from "react-router-dom";
const history = useHistory()
const match = useRouteMatch<{ id: string }>()
const toNextPage = () =>{
history.push(`/purch/table/${match.params.id}`)
}
props和withRouter的使用
import React from 'react'
import {withRouter} from 'react-router-dom'
export default function Fn1() {
return (
<div>
<h1>
Fn1
</h1>
<Mfn text='aaa'></Mfn>
</div>
)
}
const Ffn = (props) =>{
console.log(props);
return (
<div>
<h1>函数式组件Ffn</h1>
<p>{props.text}</p>
</div>
)
}
const Mfn = withRouter(Ffn)
HOOK
useState数据更改
声明一个叫 “count” 的 state 变量
[状态属性名, 修改状态的方法] = useState(状态属性的初始值);
import React,{useState} from 'react'
export default function Fn1() {
let [count,setCount] = useState(0);
let [uname,setUname] = useState('zzz')
let [uperson,setPerson] = useState({id:0,name:'ooo'})
return (
<div>
<h1>Fn1--------{count}</h1>
<p>{uname}</p>
<p>{uperson.name}</p>
<button onClick={()=>{setCount(++count)}}>点击1</button>
<button onClick={()=>{setUname('BBB')}}>点击2</button>
<button onClick={()=>{setPerson(doObj(uperson))}}>点击3</button>
</div>
)
}
const doObj = (obj)=>{
console.log(obj);
//数组对象要做整体替换,同类组件的setState的不可变值Array.concat([])
let nobj = Object.assign({},obj)
nobj.name = 'ppp'
return nobj
}
useContext数据共享
store创建React.createContext({})
//store.js
import React from 'react'
export const StoreData = React.createContext({})
export const userInfo = {
name:'曾曾曾',
age:23
}
父组件:StoreData.Provider value={user}传递
import React, { useState } from 'react'
import {StoreData,userInfo} from '../store/store'
import FnSon from './fnson'
import FnSon2 from './fnson2'
export default function Fn2() {
let [user,setUser] = useState(userInfo)
const changeName = (text) =>{
let obj = Object.assign({},user)
obj.name = text
setUser(obj)
}
return (
<div>
<StoreData.Provider value={user}>
<h1>Fn2组件</h1>
<FnSon sonClick={changeName}></FnSon>
<FnSon2></FnSon2>
<button onClick={()=>changeName('我是更改的name')}>点击更改name</button>
</StoreData.Provider>
</div>
)
}
子组件:useContext(StoreData)获取
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <xxx.Provider> 的 value prop 决定。
import React,{useContext} from 'react'
import {StoreData} from '../store/store'
export default function Fnson() {
let userInfo = useContext(StoreData)
console.log('fnson:',userInfo);
return (
<div>
<h1>Son1</h1>
<p>{userInfo.name}</p>
<p>{userInfo.age}</p>
<button onClick={()=>{props.sonClick('son Click')}}>子组件更改</button>
</div>
)
}
useEffect生命周期
该Hook 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect(()=>{},[依赖])
它在第一次渲染之后和每次更新之后都会执行
允许一个组件中,写多个useEffect();根据需要不同来写。
当状态发生改变时,都会自动去调用useEffect(),执行里面的函数。
需要保证只调用一次useEffect(): 这个时候,就要使用第二个参数。如果只是执行一次,第二个参数,可是一个空数组,这样只会在挂载结束 后,执行一次;
如果第二个参数,不是空数组,而是具体的一个变量名,需要确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
通过return 返回一个回调函数来实现清除。在componentWillUnMont()时,执行回调函数。
定时器和事件的案例:
useEffect(async () => {
let timer;
async function startTime() {
timer = setInterval(() => {
setTime(Date());
}, 1000);
console.log(timer);
document.addEventListener("scroll", test, true);
}
await startTime();
// 清除上面实现的功能的副作用;
// 通过return 返回一个回调函数来实现清除。在componentWillUnMont()时,执行回调函数。
return () => {
clearInterval(timer);
document.removeEventListener("scroll", test, true);
};
}, []);
useCallback和useRef
把普通函数变成一个缓存的函数
const changeName = useCallback((text) =>{
let obj = Object.assign({},user)
obj.name = text
setUser(obj)
},[])
const rInput = useRef()
//...
<input ref={rInput}/>
Mobx仓库
安装: npm install mobx --save
React 绑定库: npm install mobx-react --save
安装修饰器支持的插件:npm i babel-plugin-transform-decorators-legacy -D
配置babel
git add .
git commit -m ‘xxx’
npm run eject
package.json中添加plugins项
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
],
"presets": [
"react-app"
]
}
Mobx5的使用
创建store:
//userStore.js
import {observable,computed,action,makeAutoObservable} from 'mobx'
class userStoreClass{
constructor(){
//mobx 6.x 对所有可观察的状态(变量)进行描述:
makeAutoObservable(this)
}
//写出要观察的状态
@observable user={
name:'admin',
role:'管理员',
state:0
}
//表示切换用户的次数
count = 0;
//计算属性,重写了get方法
@computed get userName(){
return this.user.name;
}
//修改state的动作
@action.bound changeUser(){
this.count+=1;
if(this.count%2==0){
this.user = {
name:'admin',
role:'管理员',
state:0
}
}else{
this.user = {
name:'guest',
role:'访客',
state:1
}
}
}
}
const userStore = {userInfo:new userStoreClass()}
export default userStore;
提供仓库:
import React, { Component } from 'react'
import {Provider} from 'mobx-react'
import userStore from '../../store/userStore'
import Eg1 from './eg'
export default class Mobx1 extends Component {
render() {
return (
<div>
<Provider {...userStore}>
<Eg1></Eg1>
</Provider>
</div>
)
}
}
Mobx参数传递及事件:
//Eg1.js
import React from 'react'
import {Link,Route} from 'react-router-dom'
import {inject,observer} from 'mobx-react'
import Demo1 from './demo1'
import Demo2 from './demo2'
function Eg1(props) {
console.log(props);
return (
<div>
<h1>Mobx Eg</h1>
<h1>{props.userInfo.user.name}</h1>
<Link to='/mobx/demo1'>Demo1</Link>
<Link to='/mobx/demo2'>Demo2</Link>
<Route path='/mobx/demo1' component={Demo1}></Route>
<Route path='/mobx/demo2' component={Demo2}></Route>
</div>
)
}
export default inject('userInfo')(observer(Eg1))
函数组件和类组件中的使用:
Demo1.js类组件
import React, { Component } from 'react'
import {inject,observer} from 'mobx-react'
@inject('userInfo')
@observer
class Demo1 extends Component {
constructor(props){
super(props)
this.state = {
count:0
}
}
handChange(){
//修改mobx内的数据
this.props.userInfo.changeUser()
//修改组件内的状态
let {count} = this.state
count+=2
this.setState({
count
})
}
render() {
console.log('demo1:',this.props);
return (
<div>
<h1>Demo1</h1>
<p>Mobx的role:{this.props.userInfo.user.role}</p>
<p>Mobx的count:{this.props.userInfo.count}</p>
<p>组件内的count:{this.state.count}</p>
<div>
<button onClick={()=>{this.handChange()}}>点击触发事件</button>
</div>
</div>
)
}
}
export default Demo1;
Demo2.js函数组件:
import React,{useState} from 'react'
import {inject,observer} from 'mobx-react'
function Demo2(props) {
console.log('Demo2',props);
let [count,changeCount] = useState(0)
return (
<div>
<h1>Demo2</h1>
<p>Mobx的role:{props.userInfo.user.role}</p>
<p>Mobx的count:{props.userInfo.count}</p>
<p>组件内的count:{count}</p>
<div>
<button onClick={()=>{changeCount(handChange(props,count))}}>点击触发事件</button>
</div>
</div>
)
}
function handChange(props,count){
//修改mobx内的数据
props.userInfo.changeUser()
count+=2
return count
}
export default inject('userInfo')(observer(Demo2))
总结
创建Mobx的store:
import {observable,computed,action,makeAutoObservable} from ‘mobx’
class userStoreClass{}内再通过每一个去构建数据方法等
然后创建实例const userStore = {userInfo:new userStoreClass()}
最后export default userStore 抛出
提供Mobx
import {Provider} from ‘mobx-react’
import userStore from '…/…/store/userStore ’
Provider {…userStore} 包裹使用仓库的组件
类组件使用Mobx
import {inject,observer} from ‘mobx-react’
@inject(‘userInfo’) //依据 {…userStore}内的实例名定
@observer
class Demo1 extends Component{}
class内用this.props拿数据
export default Demo1;
函数式组件使用Mobx
import {inject,observer} from ‘mobx-react’
function Demo2(props){}
function内用props拿数据
export default inject(‘userInfo’)(observer(Demo2))
Mobx6简化版使用
不用安装babel-plugin-transform-decorators-legacy也不用配置babel
创建store.js:
import { makeAutoObservable, observable, action, computed } from "mobx";
class UserStoreClass {
user = {
name: "张三",
role: "管理员",
isGuest: "false",
};
count = 0;
constructor() {
// makeAutoObservable()不支持super()
makeAutoObservable(this, {
user: observable,
count: observable,
userName: computed,
changeUser: action.bound, //this总是被正确地在函数内部约束。
});
}
get userName() {
return this.user.name;
}
changeUser(v) {
if (this.count % 2 === 1) {
this.user = {
name: v,
role: "管理员",
isGuest: "false",
};
} else {
this.user = {
name: v,
role: "访客",
isGuest: "true",
};
}
this.count++;
}
}
const userStore = { userInfo: new UserStoreClass() };
export default userStore;
使用Mobx:直接引入store就可以使用了
import React from "react";
import { observer } from "mobx-react";
import store from "./../store";
const About = observer((props) => {
return (
<div>
<div>{store.userInfo.userName}</div>
<div>角色:{store.userInfo.user.role}</div>
<Button
onClick={() => {
store.userInfo.changeUser("余波");
}}
>
修改用户
</Button>
</div>
);
});
export default About;
Redux
npm i redux
npm i react-redux
npm install @reduxjs/toolkit
HOC高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
创建一个复用的按钮组件:
import React from 'react'
import Btneg from './Btneg'
export default function Hoc() {
return (
<div>
<h1>
HOC
</h1>
<Btneg name='我是HOC按钮'></Btneg>
</div>
)
}
//Btneg.js
import React, { Component } from 'react'
import BtnHOC from './BtnHOC'
class Btneg extends Component {
render() {
let {name,myClick} = this.props
console.log(this.props);
return (
<div>
<button onClick={myClick}>{name}</button>
</div>
)
}
}
export default BtnHOC(Btneg)
//BtnHOC.js
import React,{Component} from 'react'
const userInfo = {
role: 1,
name: "普通用户",
};
function BtnHOC(DoComponet) {
const Btn = class extends Component {
constructor(props){
super(props)
this.state = {
isShow:true,
name:props.name
}
}
doClick = () =>{
[0,1,2,3].includes(userInfo.role)?
console.log('true'):console.log('false');
}
render() {
console.log(this.props);
return(
<div>
{
this.state.isShow?
(
<DoComponet name={this.state.name} myClick={this.doClick}></DoComponet>
):
(
<DoComponet name={this.state.name} myClick={this.doClick}></DoComponet>
)
}
</div>
)
}
}
return Btn
}
export default BtnHOC
总结:
使用自己封装的组件Btneg,进行传参给BtnHOC文件
Btneg设置接收HOC的按钮数据,事件等信息
export default BtnHOC(Btneg)
在HOC中接收数据进行数据事件的处理返回组件
function BtnHOC(DoComponet) {
const Btn = class extends Component{
render() {
return(
//设置组件…
)
}
return Btn
}
export default BtnHOC
Portals
组件会默认按照既定层次嵌套渲染,如何让组件渲染到父组件以外
import ReactDom from ‘react-dom’
return ReactDom.createPortal(xxx节点,document.body)
应用场景:
父组件overflow:hidden
父组件z-index值太小
fixed放第一层级时
import React, { Component } from 'react'
import ReactDom from 'react-dom'
export default class Portals extends Component {
render() {
console.log(this.props);
let styleDiv = {
position:'fixed',
width:'100px',
height:'100px',
backgroundColor:'red',
top:0,
left:0
}
return ReactDom.createPortal(
<div style={styleDiv}>
{this.props.children}
</div>,
document.body
)
}
}
从App内部跳到了body上
context
公共信息(语言主题)如何传递给每个组件?
创建:
创建一个Context,给一个默认值
const xxx = React.createContext(‘light’)
再通过xxx.Provider 提供出去,value={this.state.theme}
//创建Context默认值
const ContextTheme = React.createContext('light')
export default class ContextDemo extends Component {
constructor(props){
super(props)
//主题的生产方
this.state = {
theme:'light'
}
}
render() {
return (
// 传输
<div>
<ContextTheme.Provider value={this.state.theme}>
<Sonbar theme={this.state.theme}/>
<button onClick={this.changeTheme}>点击切换主题</button>
<h1>Father:{this.state.theme}</h1>
</ContextTheme.Provider>
</div>
)
}
changeTheme =()=>{
this.setState({
theme : this.state.theme=='light'?'dark':'light'
})
}
}
第一层子组件:
function Sonbar(props) {
return (
<div>
<h1>Sonbar:{props.theme}</h1>
<GrandSon1 />
<GrandSon2 />
</div>
)
}
第二次子组件接收:
类组件接收:
static contextType = xxx
或者:GrandSon.contextType = xxx
通过 this.context获取值
class GrandSon1 extends Component{
static contextType = ContextTheme
render(){
return(
<h1>GrandSon1:{this.context}</h1>
)
}
}
//GrandSon.contextType = ContextTheme
函数式组件接收
xxx.Consumer 包裹{value=>返回节点数据}
function GrandSon2() {
//函数组件没有this 通过一个函数返回
return (<ContextTheme.Consumer>
{value => <h1>GrandSon2:{value}</h1>}
</ContextTheme.Consumer>)
}
完整js代码
import React, { Component } from 'react'
//创建Context默认值
const ContextTheme = React.createContext('light')
class GrandSon1 extends Component{
static contextType = ContextTheme
render(){
return(
<h1>GrandSon1:{this.context}</h1>
)
}
}
// GrandSon.contextType = ContextTheme
function GrandSon2() {
//函数组件没有this 通过一个函数返回
return (<ContextTheme.Consumer>
{value => <h1>GrandSon2:{value}</h1>}
</ContextTheme.Consumer>)
}
function Sonbar(props) {
return (
<div>
<h1>Sonbar:{props.theme}</h1>
<GrandSon1 />
<GrandSon2 />
</div>
)
}
export default class ContextDemo extends Component {
constructor(props){
super(props)
//主题的生产方
this.state = {
theme:'light'
}
}
render() {
return (
// 传输
<div>
<ContextTheme.Provider value={this.state.theme}>
<Sonbar theme={this.state.theme}/>
<button onClick={this.changeTheme}>点击切换主题</button>
<h1>Father:{this.state.theme}</h1>
</ContextTheme.Provider>
</div>
)
}
changeTheme =()=>{
this.setState({
theme : this.state.theme=='light'?'dark':'light'
})
}
}
异步组件
import()
React.lazy
React.Suspense
import React, { Component } from 'react'
const ContextDemo = React.lazy(()=> import('./ContextDemo'))
export default class LazyDemo extends Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<h1>LazyDemo</h1>
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo />
</React.Suspense>
</div>
//强制刷新查看loading
//看netWork的js加载
)
}
}
性能优化SCU
SCU:shouldComponentUpdate 默认返回true
PureComponent 和 React.memo
不可变值immutable.js
shouldComponentUpdate(nextProps,nextState){
if(nextState.count!==this.state.count){
return true //可渲染
}
return false //不可渲染
}
react 默认父组件有更新,子组件无条件也会更新
配合setState不可变值使用
PureComponent和memo
函数组件:PureComponent,SCU中实现了浅比较
immutable
基于共享数据(不是深拷贝),速度好
关于组件公共逻辑的抽离
高阶组件HOC
Render Props
import React, { Component } from 'react'
const withHoc = (Mouse)=>{
class withHocComponent extends Component {
constructor(props){
super(props)
this.state = {
x:0,
u:0
}
this.move = this.move.bind(this)
}
move(e){
this.setState({
x:e.clientX,
y:e.clientY
})
}
render() {
return (
<div onMouseMove={this.move} style={{border:'1px solid red',height:'50px'}}>
<Mouse {...this.props} mouse={this.state}/>
</div>
)
}
}
return withHocComponent
}
const Mouse = (props) =>{
const {x,y} = props.mouse
return (
<div style={{border:'1px solid black'}}>
<h1>Postion:{x},{y}</h1>
</div>
)
}
export default withHoc(Mouse)