基本引入
此项目React版本为 V17.0.2
全局安装react脚手架
npm install -g create-react-app
create-react-app reactDemo //用脚手架创建React项目
src/index.js
// 下面这两个包是react必要的两个包
import React from 'react'
import ReactDOM from 'react-dom'
// app是一个入口文件
import APP from './APP'
// 利用ReactDOM的render将APP渲染到public/index.html上 这个root就是在index.html中
ReactDOM.render(<APP />,document.getElementById('root'))
public/index.html
src/APP.js
import React,{Component} from 'react'
class APP extends Component {
render(){
return (
<div>
欢迎访问{false?'SBB':"ReactDemoaa"}项目
</div>
)
}
}
export default APP
APP里使用了{false?'SBB':"ReactDemoaa"}来将表达式注入到html中这就是React中的JSX
APP这个组件里面使用render函数将JSX渲染html中,这和vue一样在render中的顶层只能有一个html元素(要是你实在是不想加这个顶层html容器元素的话,那么可以使用Fragment,当然你也可以使用React的短语法<></>)
import React,{Component,Fragment} from 'react'
class APP extends Component {
render(){
return (
<Fragment>
欢迎访问{false?'SBB':"ReactDemoaa"}项目
欢迎访问{false?'SBB':"ReactDemoaa"}项目
</Fragment>
)
}
}
export default APP
JSX
JSX语法是JavaScript的语法扩展,是一种在JS代码中书写HTML的语法形式,与传统的JS语法相比,它具有
- 更好的性能,React通过JSX生成virtual DOM,渲染到真实的HTML上
- 更舒适的写法,React帮助我们去操作DOM,我们只需要关注业务逻辑,不需要频繁的书写
getElemntById()等等操作DOM的方法。
渲染数据到html中
APP.js
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props); // 调用父级组件 Component的constructor方法
this.state= {
value:'asasa',
}
}
render(){
return (
<Fragment>
<p>这是{this.state.value}</p>
</Fragment>
)
}
}
export default APP
响应式数据
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props);
this.state= {
value:'',
}
}
render(){
return (
<Fragment>
<p>这是{this.state.value}</p>
<input value={this.state.value} onChange={(e)=>this.inputChange(e,'aaa')}></input>
</Fragment>
)
}
inputChange=(e,val)=>{
console.log('传递的参数是:',val);
console.log('触发了事件:',e.target.value);
// this.state.value = e.target.value
this.setState({
value:e.target.value
})
}
}
export default APP
四个知识点:
- 需要进行单向数据绑定的数据在APP类中只能存储在
this.state中(这就如同在Vue里,this.data中的数据全被vue执行了双向数据绑定一样,当我们存在某个数据不需要去给vue进行双向数据绑定收集依赖时,我就无需在this.data.return()中定义) - 对于函数的参数传递,我们可以使用
onClick={(e)=>this.addItem(e,value)}的形式,e指的是原生事件对象(这里可使用ref形式去获得e.target的值),value就是我们传入的参数了 - 这里不使用
箭头函数的话,那我们还可以使用bind修改this的指向 - 对于在this.state中存储的数据,我们使用
this.setState去修改从这里我们可以看出React是单向数据流
this.setState
- this.setState是异步的
- this.setState的变化只会发生一次(同一个数据变更DOM只会更新一次)
- this.setState的第一个参数可以是一个
function,参数是上一个state和props - this.setState的第二个参数是this.setState异步操作完成后的回调 看下面的例子
import React from 'react';
interface IProps {
}
interface IState {
count:number
}
class App extends React.Component<IProps,IState> {
constructor(props){
super(props)
this.state = {
count:0
}
}
render(){
return (
<div>
<p onClick={()=>{
this.setState({count:this.state.count+1})
console.log("当前的state是:",this.state.count)
}}>点击</p>
<p>{this.state.count}</p>
</div>
)
}
}
export default App;
当我们点击按钮的时候,发现console.log没有输出count正确的结果。因为 this.setState是异步的。
那我们应该怎么做,才能输出当前正确的count?
通过this.setState的第二个参数
下面我们改一下这个代码:
import React from 'react';
interface IProps {
}
interface IState {
count:number
}
class App extends React.Component<IProps,IState> {
constructor(props){
super(props)
this.state = {
count:0
}
}
render(){
return (
<div>
<p onClick={()=>{
this.setState({count:this.state.count+1},()=>{
console.log("当前的state是:",this.state.count)
})
}}>点击</p>
<p>{this.state.count}</p>
</div>
)
}
}
export default App;
跟Vue的异步更新队列一样,**react在面对一个数据变化的过程中,只会更新一次DOM
import React from 'react';
interface IProps {
}
interface IState {
count:number
}
class App extends React.Component<IProps,IState> {
constructor(props){
super(props)
this.state = {
count:0
}
}
render(){
return (
<div>
<p onClick={()=>{
this.setState({count:this.state.count+1},()=>{
console.log("当前的state是:",this.state.count)
})
this.setState({count:this.state.count+1},()=>{
console.log("当前的state是:",this.state.count)
})
}}>点击</p>
<p>{this.state.count}</p>
</div>
)
}
}
export default App;
我们可以看到,当我们点击了一次,count并没有+2,而是+1,那是因为react在DOM没更新之前,获取到的count只是1。
那么我们应该怎么样,实现这个点击一次调用两次setState的操作呢?
答案是 通过setState的第一个参数,我们可以传入一个函数
import React from 'react';
interface IProps {
}
interface IState {
count:number
}
class App extends React.Component<IProps,IState> {
constructor(props){
super(props)
this.state = {
count:0
}
}
render(){
return (
<div>
<p onClick={()=>{
this.setState((preState,preProps)=>{
return {
count:preState.count+1
}
},()=>{
console.log("当前的state是:",this.state.count)
})
this.setState((preState,preProps)=>{
return {
count:preState.count+1
}
},()=>{
console.log("当前的state是:",this.state.count)
})
}}>点击</p>
<p>{this.state.count}</p>
</div>
)
}
}
export default App;
从上面我们可以看出,this.setState的第一个参数也可以是一个函数,函数的第一个参数是:上一个state,函数的第二个参数是:上一个props
循环列表渲染
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props);
this.state= {
value:'asasa',
list:['q','2','2','e']
}
}
render(){
return (
<Fragment>
<div>{this.state.list.map(item=>{
if(item==2){
}
return <p>{item}</p>
})}</div>
</Fragment>
)
}
}
export default APP
- 我们使用this.state.list.map函数去进列表渲染
- 在循环函数map中return出了一个html
- 我们在map中还进行了一个条件渲染,只渲染非'2'的元素 在看一个例子
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props);
this.state= {
value:'asasa',
list:['q','2','2','e']
}
this.addItem = this.addItem.bind(this)
}
render(){
return (
<Fragment>
<p>{this.state.value}</p>
<input onChange={this.change}></input>
<div onClick={this.addItem}>添加子项</div>
<div>{this.state.list.map(item=>{
return <p>{item}</p>
})}</div>
</Fragment>
)
}
addItem(){
this.setState({
list:[...this.state.list,this.state.value]
})
}
change = (e)=>{
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
如同Vue中的v-for一样,我们需要对渲染的列表添加一个key(React和Vue中的Key作用都是一样的,都是根据这个key去进行diff算法比对),看下面的例子
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props);
this.change = this.change.bind(this)
this.addItem = this.addItem.bind(this)
this.state= {
value:'asasa',
list:[
{
value:'q',
id:1
},
{
value:'w',
id:2
},
{
value:'e',
id:3
},
]
}
}
render(){
return (
<Fragment>
<p>{this.state.value}</p>
<input onChange={this.change}></input>
<div onClick={this.addItem=}>添加子项</div>
<div>{this.state.list.map((item,index)=>{
return <p key={item.id}>{item.value}</p>
})}</div>
</Fragment>
)
}
addItem(){
this.setState({
list:[...this.state.list,this.state.value]
})
}
change(e){
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
删除this.state.list中的某个元素
import React,{Component,Fragment} from 'react'
class APP extends Component{
constructor(props){
super(props);
this.change = this.change.bind(this)
this.addItem = this.addItem.bind(this)
this.state= {
value:'asasa',
list:[
{
value:'q',
id:1
},
{
value:'w',
id:2
},
{
value:'e',
id:3
},
]
}
}
render(){
return (
<Fragment>
<p>{this.state.value}</p>
<input onChange={this.change}></input>
<div onClick={this.addItem}>添加子项</div>
<div>{this.state.list.map((item,index)=>{
return <p key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
})}</div>
</Fragment>
)
}
addItem(){
this.setState({
list:[...this.state.list,this.state.value]
})
}
delItem(index){
const list = this.state.list;
list.splice(index,1);
this.setState({
list:list
})
}
change(e){
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
- 我们使用delItem删除了下标为index的list元素,这里我们还是使用的this.setState去修改this.state中的元素
添加CSS样式
新建一个src/ndex.css
.p-c{
color: brown;
}
在APP.js中引入
import React,{Component,Fragment} from 'react'
import './index.css'
class APP extends Component{
constructor(props){
super(props);
this.change = this.change.bind(this)
this.state= {
value:'asasa',
list:[
{
value:'q',
id:1
},
{
value:'w',
id:2
},
{
value:'e',
id:3
},
]
}
}
render(){
return (
<Fragment>
{/* 这是注释 */}
<p className={this.state.value==1?'p-c':''}>{this.state.value}</p>
<input onChange={this.change}></input>
</Fragment>
)
}
change(e){
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
- 我们在render中的html中,使用
className去给html元素设置样式 - 在React的JSX语法中,我们在
className中添加了三元运算符,当this.state.value===1时,添加p-r,否则就什么也不添加
添加行内style样式
import React,{Component,Fragment} from 'react'
import './index.css'
class APP extends Component{
constructor(props){
super(props);
this.change = this.change.bind(this)
this.addItem = this.addItem.bind(this)
this.state= {
value:'asasa',
list:[
{
value:'q',
id:1
},
{
value:'w',
id:2
},
{
value:'e',
id:3
},
]
}
}
render(){
return (
<Fragment>
{/* 这是注释 */}
{/* 我们给这个P标签加了一个行内样式 */}
<p style={{fontSize:20px}}>{this.state.value}</p>
<input onChange={this.change}></input>
<div onClick={this.addItem}>添加子项</div>
<div>{this.state.list.map((item,index)=>{
return <p className="p-c" key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
})}</div>
</Fragment>
)
}
addItem(){
this.setState({
list:[...this.state.list,this.state.value]
})
}
delItem(index){
const list = this.state.list;
list.splice(index,1);
this.setState({
list:list
})
}
change(e){
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
- 从上我们可以看到,我们给p元素添加了一个行内样式,p的style我们传入了一个对象
- 在React中,我们给元素添加的style是一个对象,这个对象中的css是小驼峰的写法
组件化开发
如同Vue一样,React也推崇组件化开发 我们创建一个menu.jsx(.js后缀也行的)
import React, { Component } from 'react';
class Menu extends Component {
state = {
list:[
{
value:'栏目1',
id:1
},
{
value:'栏目2',
id:2
},
{
value:'栏目3',
id:3
},
]
}
render() {
return (
<div>
{
this.state.list.map(item=>{
return <p key={item.id}>{item.value}</p>
})
}
</div>
);
}
}
export default Menu;
在APP.js中引入
import React,{Component,Fragment} from 'react'
import './index.css'
import Menu from './menu'
class APP extends Component{
constructor(props){
super(props);
this.change = this.change.bind(this)
this.addItem = this.addItem.bind(this)
this.state= {
value:'asasa',
list:[
{
value:'q',
id:1
},
{
value:'w',
id:2
},
{
value:'e',
id:3
},
]
}
}
render(){
return (
<Fragment>
<p>{this.state.value}</p>
<input onChange={this.change}></input>
<div onClick={this.addItem}>添加子项</div>
{/* <div>{this.state.list.map((item,index)=>{
return <p className="p-c" key={item.id} onClick={this.delItem.bind(this,index)}>{item.value}</p>
})}</div> */}
<div><Menu ></Menu></div>
</Fragment>
)
}
addItem(){
this.setState({
list:[...this.state.list,this.state.value]
})
}
delItem(index){
const list = this.state.list;
list.splice(index,1);
this.setState({
list:list
})
}
change(e){
console.log('触发了change函数');
this.setState({
value:e.target.value
})
}
}
export default APP
- 这里有一个小细节,我们在顶层
Fragment中是不可以直接使用Menu的,需要包裹一层容器才行
父子组件交互
这里通过一个完整的例子演示了几种情况
- 父传子---通过props传递
- 子传父---通过触发父组件的函数 APP.js
import React,{Component,Fragment} from 'react'
import './index.css'
import Menu from './menu'
class APP extends Component{
constructor(props){
super(props);
this.delItem = this.delItem.bind(this);
this.change = this.change.bind(this);
this.addItem = this.addItem.bind(this);
this.state= {
tempValue:null,
inputDOM:null,
value:'asasa',
}
}
render(){
return (
<Fragment>
<p>{this.state.value}</p>
{/*
这里的input使用了ref 代替原生的e.target
*/}
<input ref={(inputDOM)=>this.inputDOM=inputDOM} onChange={this.change}></input>
<div onClick={this.addItem}>添加子项</div>
<div><Menu content="父组件传入的值" addItem={this.state.tempValue} delItem={this.delItem}></Menu></div>
</Fragment>
)
}
addItem(){
this.setState({
tempValue:this.state.value
})
}
delItem(value){
console.log('触发了父组件函数',value);
this.setState({
value:""
})
}
change(e){
console.log('触发了change函数');
this.setState({
value:this.inputDOM.value // 我们这里使用了ref对象取代了原生的e.target
})
}
}
export default APP
menu.js
import React, { Component } from 'react';
import propTypes from 'prop-types' // 对传入的props进行校验 跟Vue的props差不多
class Menu extends Component {
state = {
list:[
{
value:'栏目1',
id:1
},
{
value:'栏目2',
id:2
},
{
value:'栏目3',
id:3
},
]
}
constructor(props){
super(props)
this.delFatherItem = this.delFatherItem.bind(this);
}
render() {
return (
<div>
{
this.state.list.map(item=>{
return <p key={item.id}>{item.value}</p>
})
}
<p>这是:{this.props.content}</p>
<p>这是:{this.props.addItem}</p>
<div onClick={this.delFatherItem}>点击这里删除父组件的值</div>
</div>
);
}
delFatherItem(){
console.log('点击了删除。。。');
this.props.delItem(this.state.list[0]);
}
}
Menu.propTypes = {
content:propTypes.string.isRequire, // 规定content只能为string类型且为必传
delItem:propTypes.func, //规定delItem只能为function类型
addItem:propTypes.string
}
export default Menu;
从上面我们可以看出
- 父传子可通过props
- 子传父可通过props获取父组件的方法,之后触发父组件的方法
- 增加ref绑定(如Vue中的ref)能获取这个DOM对象
- 对props我们可以增加
import propTypes from 'prop-types'校验(就像Vue中的props校验一样)
插槽
不具名插槽
如同Vue中的插槽一样,React里也有插槽(只能说是类似插槽,React更喜欢将组件间的数据交互用props去表示)
这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递 父组件往子组件标签内插入html,子组件通过{this.props.children}渲染
此时的插槽作用域是父组件的作用域,看下面的例子 APP.js
import React,{Component,Fragment} from 'react'
import Menu from './menu'
class APP extends Component{
constructor(props){ //组件生成
super(props);
this.state= {
value:'asasa',
}
}
render(){ // 组件挂载中 当state、props发生变化就会执行
console.log('APP组件正在渲染。。');
return (
<Fragment>
<p>{this.state.value}</p>
<Menu content="父组件的值" >
<h1>这是父组件的{this.state.value}</h1>
</Menu></div>
</Fragment>
)
}
}
export default APP
menu.js
import React, { Component } from 'react';
import propTypes from 'prop-types'
class Menu extends Component {
state = {
}
constructor(props){
super(props)
}
// shouldComponentUpdate 函数可让子组件是否会渲染 return Boolean
// 参数:newProps---新的更改的props newState 新的更改的state
shouldComponentUpdate(newProps,newState){
if(newProps.children!== this.props.children||newProps.content!==this.props.content){
return true
}else {
return false
}
}
render() {
console.log('menu组件的渲染。。。')
return (
<div>
<p>这是:{this.props.content}</p>
{this.props.children}
</div>
);
}
}
Menu.propTypes = {
content:propTypes.string.isRequired,
}
export default Menu;
我们在APP组件内的Menu组件中插入一段JSX,这段JSX中输出了APP组件的this.state.value
具名插槽
既然有匿名插槽,那就代表有具名插槽。
其实具名插槽就跟props一样,就是使用props传入一个数据,子组件渲染而已
app.js
import React,{Component} from 'react'
import Children from "./children"
import "./App.css"
class APP extends Component{
constructor(props){
super(props)
this.state = {
value:1,
msg:"我是app组件的数据"
}
this.initBind();
}
initBind(){
this.changeValue = this.changeValue.bind(this)
}
render(){
return (
<div>
<p>state中的value是:{this.state.value}</p>
<Children clickFather={this.changeValue} value={this.state.msg} content={<div>具名插槽</div>}>
<h1>这是slot。。。</h1>
</Children>
</div>
)
}
changeValue(val){
console.log("看看参数:",val)
this.setState({
value:this.state.value+1
})
}
}
export default APP
children.js
const Children = (props)=>{
return (
<>
<div>我是father组件</div>
<div>{props.value}</div>
<p onClick={()=>props.clickFather(123456)}>点击</p>
{props.children}
{props.content}
</>
)
}
export default Children
我们通过props.content传入一段jsx,这就是具名插槽
生命周期
官网链接:projects.wojtekmaj.pl/react-lifec…
相关内容在官网中,此不在赘述。
这里我只会讲解几个比较重要的生命周期函数。
constructor
组件初始化入口,主要作用就是:设置state数据,更改方法的this指向(bind去更改,或者使用箭头函数),只会执行一次
constructor(props){ //组件生成
console.log('APP组件即将执行')
super(props);
this.delItem = this.delItem.bind(this);
this.change = this.change.bind(this);
this.addItem = this.addItem.bind(this);
this.state= {
tempValue:null,
inputDOM:null,
value:'asasa',
}
}
render
正如我们上面看到的一样,render函数返回一串JSX,React会通过这个生成虚拟DOM,和真实DOM进行diff比对之后更新
shouIdComponentUpdate
这个生命周期用于组件优化
这个生命周期发生在数据更新时(props更新、state更新)
我们看上面的例子,当APP组件中渲染了Menu组件,那么当APP组件的props和setState调用时,render函数就会重新执行,这时候menu组件明明没有数据变化,但是也会被重新渲染了!!!。
因此,我们可以使用shouIdComponentUpdate可以决定子组件Menu是否会被重新渲染。
// Menu组件中
// shouldComponentUpdate 函数可让子组件是否会渲染 return Boolean
// 参数:newProps---新的更改的props newState 新的更改的state
shouldComponentUpdate(newProps,newState){
console.log('newProps.addItem:',newProps.addItem);
if(newProps.addItem!== this.props.addItem){ // 当props中的addItem没有变化时就不会重新渲染Menu组件
return true
}else {
return false
}
}
ComponentDidMount
这个生命周期发生于,组件挂载阶段,此时虚拟DOM已经渲染到HTML(插入到DOM树)中
这个生命周期的作用就是:引入相关的axios依赖(记得要在componentWillUnmount中注销掉相关实例)、当你的state依赖渲染好的DOM元素,那么就需要在这个生命周期操作,但是请注意,会导致render执行两次,且会有性能问题
/**
* 组件挂载完成 只触发一次
* 适合加入axios等实例对象 ---- 要在componentWillUnmount中删除相关实例对象
* 如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理
*/
import React,{Component,Fragment} from 'react'
import './index.css'
import axios from 'axios'
class APP extends Component{
async componentDidMount(){
console.log('组件挂载完成。。');
this.setState({ // 由于调用了setState 所以此时的render会渲染两次
value:'12121'
})
try {
const res = await axios.get('http://localhost:8088/test')
console.log('res是:',res);
} catch (error) {
console.log('请求失败,错误是:',error);
}
}
}
export default APP
componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。 这个生命周期最经典的用法就是官网上介绍的,我们可以通过比对,旧值和新值去决定是否要发送Ajax请求
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它
无状态组件
无状态组件(Stateless Functional Component)又称之为函数组件
为什么我们先需要无状态组件?
因为无状态组件相对于类组件更为简单(React也能让无状态组件与类组件完美配合)
举个例子,我们需要渲染循环列表
类组件这样做
import React, { Component } from 'react';
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
movies:[{_id:1121,title:'我是title1'},{_id:15451,title:'我是title2'}]
}
}
render() {
return (<div>
<ul>
{
this.state.movies.map(i=><li key={i._id}>{i.title}</li>)
}
</ul>
</div> );
}
componentDidMount(){
}
}
export default Menu;
无状态组件是这么做的
const Menu = (props)=>{
console.log('看看props',props);
return (<div>
<ul>
{
props.value.map(i=><li key={i._id}>{i.title}</li>)
}
</ul>
</div> );
}
export default Menu;
在调用无状态组件的父级组件:
<NavBar value={[{_id:1121,title:'我是title1'},{_id:15451,title:'我是title2'}]}></NavBar>
两个地方需要注意 1.无状态组件没有自己的state,所以我们只能使用props,由父组件替我们暂时保存值 2.无状态组件没有自己的生命周期
无状态组件传值
默认,子组件是无状态组件
父传子 ------ 通过props进行传值
app.js
import React,{Component,Fragment} from 'react'
import Children from "./Children"
import "./App.css"
class APP extends Component{
constructor(props){
super(props)
this.state = {
value:1,
msg:"我是app组件的数据"
}
this.initBind();
}
initBind(){
}
render(){
return (
<div>
<p>state中的value是:{this.state.value}</p>
<Children value={this.state.msg}></Children>
</div>
)
}
}
export default APP
Children.js
const Children = (props)=>{
return (
<>
<div>我是father组件</div>
<div>{props.value}</div>
<p onClick={()=>props.clickFather(123456)}>点击</p>
</>
)
}
export default Children
从上面我们可以看到,我们使用props往无状态组件中传入了父组件的数据
子传父 ----- 通过调用父的函数
app.js
import React,{Component,Fragment} from 'react'
import Children from "./children"
import "./App.css"
class APP extends Component{
constructor(props){
super(props)
this.state = {
value:1,
msg:"我是app组件的数据"
}
this.initBind();
}
initBind(){
this.changeValue = this.changeValue.bind(this)
}
render(){
return (
<div>
<p>state中的value是:{this.state.value}</p>
<Children clickFather={this.changeValue} value={this.state.msg}></Children>
</div>
)
}
changeValue(val){
console.log("看看参数:",val)
this.setState({
value:this.state.value+1
})
}
}
export default APP
children.js
const Children = (props)=>{
return (
<>
<div>我是father组件</div>
<div>{props.value}</div>
<p onClick={()=>props.clickFather(123456)}>点击</p>
</>
)
}
export default Children
当我们点击按钮时,发现子组件的数据通过函数clickFather传递到了父组件
祖父组件传值
使用React提供的Provider及Consumer可实现:
zh-hans.reactjs.org/docs/contex… context.js
import React from "react"
const {Provider,Consumer} = React.createContext(null);
export {
Provider,
Consumer
}
Father.jsx
import React,{useState} from 'react'
import Son from "./Son"
import {Provider} from "./context"
const Father = (props)=>{
const [info,setInfo] = useState("Fahter info")
return (
<>
<p>{info}</p>
<Provider value="Fathern provider info">
<Son></Son>
</Provider>
</>
)
}
export default Father
Son.jsx
import React,{useState} from 'react'
import GrandSon from './GrandSon'
import {Consumer} from './context'
const Son = (props)=>{
const [info,setInfo] = useState("Son info")
return (
<>
<p>{info}</p>
<Consumer>
{
(val)=>{
return (
<div>
<p> 我是Son中的信息:{val}</p>
<GrandSon></GrandSon>
</div>
)
}
}
</Consumer>
</>
)
}
export default Son
GrandSon.jsx
import React,{useState} from 'react'
import {Consumer} from "./context"
const GrandSon = (props)=>{
const [info,setInfo] = useState("GrandSon info")
return (
<>
<p>{info}</p>
<Consumer>
{
(val)=>(
<div>
我是GrandSon中的信息:{val}
</div>
)
}
</Consumer>
</>
)
}
export default GrandSon
react-router
react中有三个包能够让我们管理路由
react-router:包含最简单的router功能,一般不使用react-router-native:在react-native的应用中会用到react-router-dom:浏览器端一般使用这个 安装:
npm install react-router-dom
我这里安装的是v6.2.1版本的
在APP中编写路由相关处理逻辑:
import React, { useState } from 'react'
import Father from './components/Father';
import Son from "./components/Son"
import GrandSon from "./components/GrandSon"
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const APP = ({})=>{
const [info,setInfo] = useState('init APP');
return (
<div>
{info}
<Routes>
<Route path='/Father' element={<Father />} />
<Route path='/Son' element={<Son />} />
<Route path='/GrandSon' element={<GrandSon />} />
</Routes>
</div>
)
}
export default APP
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {BrowserRouter} from 'react-router-dom'
// React.StrictMode 代表的是 react 的 严格模式 帮助我们检测过时的API,语法问题等
ReactDOM.render(
<React.StrictMode>
<BrowserRouter> // 使用html5 的路由模式 也可换成vue的hans模式:HashRouter
<App name="React"/>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
- 我们可以使用
useSearchParams()钩子来获取/Father?name=arzhu中的?后的值 - 我们可以使用
useNavigate()钩子进行编程式路由跳转 - 我们可以使用
useParams()钩子获取/Father/123中的123,在路由中需要配置为:<Route path='/Father/:id' element={<Father />} />更多内容请参考
官方文档:react-guide.github.io/react-route…
zhuanlan.zhihu.com/p/431389907
HOC组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
具体可看:juejin.cn/post/687250…
主要的作用是
1.复用组件逻辑
2.代理操作
主要的实现方式是:属性代理、反向继承
属性代理
我们使用一个函数,return出一个组件A,函数的参数就是需要被HOC的组件A,我们在这个函数中可以进行许多操作:
- 操作props,添加公共属性
- 控制渲染
- 复用公共逻辑(操作state,添加生命周期逻辑等)
操作props,添加公共属性
新建withHOCson.jsx
import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
return class extends React.Component { // 返回了一个匿名函数
render() {
const newProps = {...this.props,type:456};
return ( // 匿名函数渲染了 参数组件
<>
<WrappedComponent {...newProps}></WrappedComponent>
</>
)
}
}
}
export default withHOCson
新建Son.jsx
import React,{useState} from 'react'
import withHOCson from "./withHOCson"
const Son = (props)=>{
return (
<>
<div>props中的id:{props.id}</div>
<div>SON INFO</div>
</>
)
}
const ExplameCom = withHOCson(Son)
export default ExplameCom
app.jsx中
<ExplameCom id={123}></ExplameCom>
从上面我们可以看到,在withHOCson中,我们返回了一个组件,并且,将props注入了一个共有属性type
当然了,上面的Son.jsx换成class类组件也是一样的:
import React,{useState} from 'react'
import withHOCson from "./withHOCson"
class Son extends React.Component {
render () {
console.log(this.props);
return (
<>
<p>props:{this.props.id}</p>
<div>SON INFO</div>
</>
)
}
}
const ExplameCom = withHOCson(Son)
export default ExplameCom
控制渲染
在withHOCson中,我们修改如下,其余不变
import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
return class extends React.Component {
render() {
const newProps = {...this.props,type:456};
return (
<>
<div>我是HOC的信息</div>
<WrappedComponent {...newProps}></WrappedComponent>
</>
)
}
}
}
export default withHOCson
我们在withHOCson中写入了一段jsx,这就算是在HOC中渲染的共有jsx部分,当然了我们也可以通过props传入一个flag,控制某些组件jsx隐藏或显示
修改withHOCson:
import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
return class extends React.Component {
render() {
const newProps = {...this.props,type:456};
return (
<>
{this.props.flag===true&&<div>我是HOC的信息</div>}
<WrappedComponent {...newProps}></WrappedComponent>
</>
)
}
}
}
export default withHOCson
修改app.jsx:
<ExplameCom id={123} flag={true}></ExplameCom>
可以看到,本质上,HOC跟我们处理别的组件也是类似的,但是不同的是,我们通过使用HOC能达到,复用逻辑的作用
复用公共逻辑
这里我们举一个受控组件的例子,受控组件指的是那些,组件状态由用户的操作决定的组件,比如input,select等等
场景:我们需要使用input做一个双向数据绑定的组件,此外,还需要输入一些额外的信息,我们可以想到,使用组件props很容易实现,但是我们另一个组件也需要实现这个需求,难道我们要将代码C+V么?显然是不正确的,于是我们考虑使用HOC去实现
withHOCson.jsx
import React,{useState} from 'react';
const withHOCson = (WrappedComponent)=>{
return (props)=>{ // 这里我使用了 hooks 的写法
const [value,setVale] = useState(props.originVal)
const change = (e)=>{
setVale(e.target.value);
}
const newProps = {
...props,
type:456,
value:value,
change:change
};
return (
<>
<WrappedComponent {...newProps}></WrappedComponent>
</>
)
}
}
export default withHOCson
Son.jsx
import React, { useState } from 'react'
import withHOCson from "./withHOCson"
const Son = (props) => {
return (
<>
<p>props的id:{props.id}</p>
<p>props的value:{props.value}</p>
<div>SONC INFO</div>
<input value={props.value} onChange={props.change}></input>
</>
)
}
const ExplameCom = withHOCson(Son)
export default ExplameCom
Sonc.jsx
import React, { useState } from 'react'
import withHOCson from "./withHOCson"
const Sonc = (props) => {
return (
<>
<p>props的id:{props.id}</p>
<p>props的value:{props.value}</p>
<div>SONC INFO</div>
<input value={props.value} onChange={props.change}></input>
</>
)
}
const ExplameComc = withHOCson(Sonc)
export default ExplameComc
app.jsx
<ExplameCom id={123} originVal={"我是ExplameCom组件"}></ExplameCom>
<ExplameComc id={456} originVal={"我是ExplameComc组件"}></ExplameComc>
我们在withHOCson中根据传入的props.orginVal作为input的value初始值,在input的onCahnge事件中调用了HOC中定义的propschange事件,这个事件内部调用了setVale更新value的值。
我们对两个组件Son、Sonc中的双向绑定功能,进行了逻辑复用
反向继承
反向继承跟属性代理的思路不同,反向继承是在HOC中返回一个继承了HOC参数组件的类组件,这么一说很拗口难懂,所以直接看例子:
Sonc.jsx
import React, { useState } from 'react'
import withReverse from "./withReverse"
class Sonc extends React.Component {
constructor(){
super()
this.state = {
msg:'这是 Sonc 的信息'
}
}
render(){
return (
<>
<p>SONC INIT</p>
</>
)
}
}
const ExplameComc = withReverse(Sonc)
export default ExplameComc
withReverse.jsx
import React from 'react'
const withReverse = (WrappedComponent)=>{
return class extends WrappedComponent{
render (){
return super.render(); // 咱们直接通过 super.render()调用父类的render
}
}
}
export default withReverse
app.jsx
<ExplameComc></ExplameComc>
从上面我们可以看到,反向继承的定义是 函数中返回一个继承了函数参数的匿名组件
反向继承还可以操作父类组件中的state
withReverse.jsx
import React from 'react'
const withReverse = (WrappedComponent)=>{
return class extends WrappedComponent{
constructor(props){
super(props);
}
render (){
return (
<>
<p>HOC中的信息:{this.state.msg}</p>
<p onClick={()=>this.change()}>点我改变msg</p>
{/* 渲染 父类的render */}
{super.render()}
</>
);
}
change(){
this.setState({
msg:'HOC msg'
})
}
}
}
export default withReverse
Sonc.jsx
import React, { useState } from 'react';
import withReverse from "./withReverse"
class Sonc extends React.Component {
constructor(){
super()
this.state = {
msg:'sonc state msg'
}
}
render(){
return (
<>
<p>Sonc中的信息:{this.state.msg}</p>
<p>SONC INIT</p>
</>
)
}
}
const ExplameComc = withReverse(Sonc)
export default ExplameComc
app.jsx不变
当我们点击点我改变msg这个dom的时候,我们发现Sonc和withReverse中的msg都发生了改变!!! 这就证明Sonc和withReverse共用了一个state 这是一件很危险的事情,这会让数据变更来源不明,不符合react的单项数据流定义
反向继承的主要作用有:
- 操作state
- 渲染劫持 第一个作用在我们上面的例子已经演示过了。
渲染劫持
何谓渲染劫持?简单的回答就是
修改目标组件的渲染功能
我们之前使用属性代理实现的控制渲染也是属于渲染劫持的一种
再回顾一下我们使用反向继承的原理:一个HOC中返回了一个继承了参数组件的匿名组件,内部可以使用this访问组件的state,生命周期等
那么我们在这个HOC中也可以对参数组件做渲染劫持,渲染出我们希望渲染的数据
render函数的底层实际上是调用了React.createElement()产生的新的React元素,我们做渲染劫持,其实就是去操作这个React元素。
withReverse
import React from 'react'
const withReverse = (WrapComponent)=>{
return class extends WrapComponent {
componentDidMount () {
setTimeout(() => {
console.log(this.props)
}, 2000)
}
render () {
let testRender = super.render();
console.log(testRender) // 打印看看 render函数返回的对象
let newProps = { value: '渲染劫持了!!!' }
let allProps = Object.assign({}, testRender.props, newProps); // 创建一个新的props
let finalRender = React.cloneElement(
testRender,
allProps,
testRender.props.children
);
return finalRender
}
}
}
export default withReverse
Sonc.jsx
import withReverse from "./withReverse"
import React from "react"
class TestComponent extends React.Component {
constructor(props){
super(props);
this.state= {
msg:'Sonc Init',
val:1111
}
}
render () {
return (
<>
<input value={this.state.msg} readOnly={true}></input>
</>
)
}
}
export default withReverse(TestComponent)
app.jsx
<ExplameComc></ExplameComc>
withReverse中我们使用了一个APIReact.cloneElement
以
element元素为样板克隆并返回新的 React 元素。config中应包含新的 props,key或ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在config中未出现key或ref,那么原始元素的key和ref将被保留
可以看到我们渲染劫持了Sonc的数据
HOC组件帮我们实现了React中的逻辑复用,但是也会产生一些让人头疼的问题
如何使用HOC
1、单个使用
当我们只需要一个HOC时,就可以像之上面的例子一样,直接调用export default Explate = withHOC(Test)
2、多个使用
当我们使用多个HOC时,那我们应该如何调用呢?相信你第一个想到的就是直接调用,简单粗暴:
export default Explate = withHOC1(withHOC2(withHOC3(Test)))
但是这种方式太傻了点,也不够骚。
这里介绍一个compose函数,它的作用是,按照参数的传入方式,依次调用
const compose = (...fns) => (...args) => fns.reduce((val, fn) => fn.apply(null, [].concat(val)), args);
那么我们就可以这么使用了:
const Example = compose(HOC1,HOC2,HOC3)(TestComponent)
约定
使用HOC的时候,为了让我们的代码结构更清晰,BUG数量更少,我们应该遵循下面的约定
- 透传props :在HOC中应该透传与自己无关的props
- 避免直接修改
state:在使用HOC的反向继承实现时,我们可以轻易的去更改state,但是!!!千万不要那么做,这么做会让我们写的组件让人难以理解和调试 - 不要在render()中创建HOC:原因是于React的diff有关(感兴趣的可自行了解)
- 拷贝组件的静态方法:因为HOC返回的是一个全新的组件,所以经过HOC包装的组件将没有之前定义的静态方法,我们可以使用
hoist-non-react-statics拷贝所有静态方法 - 使用displayName:我们一般使用
React Developer Tools调试React,但是当我们使用了HOC之后就会难以调试,因此我们可以给HOC添加一个displayName属性,代表当前的HOC
缺点
- 当我们组件调用大量的HOC的时候,会形成一种HOC嵌套地狱的困境
- 由于HOC可以做到
渲染劫持修改props,state等的功能,所以会导致我们的代码难以接手
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 我个人理解就是在不使用类组件的情况下,在无状态组件中使用类组件的方法和生命周期 看一个官网的例子吧:
count.js
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。它的初始值位为 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Example;
app.js像正常的函数组件那样子去调用
import Example from './count'
<Example></Example>
从这个例子我们可以看出
- 设置一个变量:
const [变量名,更改变量的方法(类似setState)] = useState([初始化变量]) - 我们使用从useState中得到的方法对变量去进行更新。
useState就是一个hook钩子
副作用
在先理解副作用之前,我们先理解什么是纯函数
纯函数即:给一个函数相同的参数,这个函数永远返回一样的结果,在react中,我们推崇,函数式编程,这个函数式编程在react中体现的就是一个组件输入相同的props那么我们的逻辑和渲染UI都应该是一致的。
副作用的含义就是,在纯函数中,虽然输入了相同的参数,但是却不一定返回相同的值,这一点在react中表现为,输入相同的props,但是逻辑和UI或许不一致,那么我们这种影响了纯函数输出唯一结果的操作(处理了与返回值无关的操作) 就是副作用
举个例子:
- 我们在一个组件中需要发送一个ajax请求,这个ajax的数据是存储在后端的,那么当我们给这个组件输入一个props,我们能保证每次后端返回的数据都是原来的数据么?不能保证吧?所以这就是副作用
- 一个组件输入的props,但是我们在组件中使用了
console.log等与返回值无关的函数,这也是副作用
Hook钩子
在React的hook中,钩子类型分为
- React钩子:
- 自定义钩子:
常用的钩子
useState
这个钩子的作用是能够让我们在无状态组件中使用this.state
基本语法:
const [状态,更改状态的函数] = useState(状态的初始值)
例子:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。它的初始值位为 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Example;
我们声明了一个变量count和一个更改count的函数setCount,在无状态组件中,我们 不需要使用this 去调用count,我们不再使用 this.setState 去更新count 转而使用 setCount 去更新count
useEffect
- 取代
componentDidMount、componentDidUpdate和componentWillUnmount - 给函数添加副作用
- React能保证这个钩子在DOM更新之后再执行
useEffect的第一个参数的回调
模拟componentDidUpdate(伪)
我们这里设计的componentDidUpdate(伪)并不是一个真正意义上的componentDidUpdate,因为我们设计的这个componentDidUpdate(伪)会在组件渲染的时候,初始化一次,真正的componentDidUpdate是不会在组件初始化的时候执行的,后续我们通过useRef+useEffect实现一个真正的componentDidUpdate
useEffect(()=>{
变更之后的处理(即,触发了componentDidUpdate的处理)
},[需要追踪变更的状态列表])
我们将实现网页的title随着count变更的需求
import React, { useState, useEffect } from 'react';
const App: React.FC = () => {
const [count, setCount] = useState<number>(0)
useEffect(()=>{
document.title=`点击了${count}次`
},[count])
return (
<div >
<button
onClick={() => {
setCount(count + 1)
}}
>
Click
</button>
<span>count: {count}</span>
</div>
)
}
export default App;
这个例子中useEffect传入了两个参数,第二个参数是一个数组,这个数组中存放了会被监听的状态,当这个状态改变时,我们就会触发useEffect的第一个函数 。
模拟componentDidMount
基本语法
useEffect(()=>{
在组件挂载后渲染一次,可作用与ajax,类似 componentDidMount
},[])
实现一个ajax请求
import React, { useState, useEffect } from 'react';
const App: React.FC = () => {
const [robotGallery, setRobotGallery] = useState<any>([])
// 第二个参数传入一个空数组 那就代表,第一个参数只会在渲染的时候触发一次
useEffect(()=>{
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setRobotGallery(data));
},[])
return (
<div >
<div >
{robotGallery.map((r) => (
<p key={r.id}>{r.name}</p>
))}
</div>
</div>
)
}
export default App;
从上面我们可以看到,我们将useEffect的第二个参数设置为[]空数组,此时,第一个参数只有在组件挂载之后调用一次。
useEffect第一个参数函数返回值
使用这个函数返回值,能让我们模拟出componentWillUnmount操作
useEffect的第一个参数可以返回一个函数,当页面渲染了下一次更新的结果后,执行下一次useEffect之前,会调用这个函数。这个函数常常用来对上一次调用useEffect进行清理。 React能保证 在DOM 更新之后再执行 回调函数
import React,{useState,useEffect} from 'react'
import Dep from './Dep'
export default function Father (props){
const [info,setInfo] = useState("Fahter info")
const [count,setCount] = useState(1)
const callback = ()=>{console.log('Dep的callback')}
// 假设有一个发布订阅 类 dep
useEffect(()=>{
console.log("订阅")
Dep.on('init',callback)
return ()=>{
// 删除订阅
Dep.off('init',callback)
console.log("执行清除")
}
},[count])
const params = useParams();
console.log("Father渲染。。。")
return (
<>
<p>count:{count}</p>
<p onClick={()=>{setCount(count+1)}}>点击更改count</p>
</>
)
}
可以看到,我们每次点击更改时,都会让count+1,然后useEffect中执行第一个参数函数,当下一次count发生变化时(页面渲染之后),就会执行上一次的参数函数回调,再执行这次的useEffect的第一个参数函数,从而达到清除的作用。
执行顺序为:
count+1 ---> 页面渲染 ---> 执行上一个 useEffect 回调 ---> 执行这次的 useEffect 参数函数
useEffect 使用 async/await
useEffect的第一个参数是不能为async函数的,那么我们应该怎么在useEffect中使用async呢?
import React, { useState, useEffect } from 'react';
const App: React.FC = () => {
const [robotGallery, setRobotGallery] = useState<any>([])
useEffect(()=>{
const fetchData = async()=>{
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json()
setRobotGallery(data)
}
fetchData();
},[])
return (
<div >
<div >
{robotGallery.map((r) => (
<p key={r.id}>{r.name}</p>
))}
</div>
</div>
)
}
export default App;
我们可以包装一个async 函数 在useEffect中使用就行了
useContext
在类组件中,我们通过Provider的特性,可以跨组件通信,而使用了无状态组件,我们也可以使用useContext来实现跨组件通信:
新建一个src/components/data
const defaultContext = {userName:"啊祝"}
export default defaultContext
新建一个src/components/appContext
import React from "react"
import defaultContext from "./data"
const appContext = React.createContext(defaultContext);
export default appContext
在我们的index中写入:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import appContext from "./context/appContext"
import defaultContext from "./context/data"
ReactDOM.render(
<React.StrictMode>
<appContext.Provider value={defaultContext}> // 注入
<App />
</appContext.Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
在子代组件中,如果是不用hook的话我们是这么做的:
import React from "react";
import appContext from "../context/appContext"
const Roboot> =({id,name,email})=>{
return (
<appContext.Consumer>
{
(value)=>{
return (
<div >
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{value.userName}</p>
</div>
)
}
}
</appContext.Consumer>
)
}
export default Roboot;
使用了useContext我们是这么做的
import React,{useContext} from "react";
import appContext from "../context/appContext"
const Roboot> =({id,name,email})=>{
const value = userContext(appContext)
return (
<div >
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{value.userName}</p>
</div>
)
}
export default Roboot;
从上面的代码我们可以看到:我们使用useContext简化了代码,提升了效率
useRef
使用useRef能获取到ref对象,ref代表了当前的对象或者是组件实例:
useRef返回一个可变的 ref 对象,其.current属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在
import React, { useState, useRef } from 'react'
const GrandSon = (props) => {
const [value, setValue] = useState("qwersss")
const inputEl = useRef(null); //
const onButtonClick = () => {
console.log(inputEl.current.value); // 获取到了 value 的值
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
console.log("GrandSon执行。。。")
return (
<>
<p>{value}</p>
<input ref={inputEl} value={value} type="text" onChange={(e)=>setValue(e.target.value)}/>
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
export default GrandSon
我们使用了 useRef 设置了 inputEl 为 input 元素的 ref对象,相当于 类组件的 React.createRef()
我们结合useEffect和useRef可以模拟componentDidUpdate
模拟componentDidUpdate
const GrandSon = (props) => {
const [count,setCount] = useState(0);
const init = useRef(true);
useEffect(()=>{
if(init.current){ // 当第一次进来时
init.current = false; // 设置为false 那么接下来的 useEffect都会走 else
}else{
document.title = `count:${count}`
}
},[count])
return (
<>
<p>count:{count}</p>
<p onClick = {()=>{setCount(count+1)}}>chang Count</p>
</>
)
}
export default GrandSon
首先我们先明确一个概念,就是componentDidUpdate是除去第一次初始化之后的useEffect。那么我们可以使用useRef给一个值初始值true,当第一次进入useEffect中,就将这个值置否,就能实现模拟componentDidUpdate
自定义hooks
对于公共逻辑我们可以做成一个自动义hook,比如:日志输出,双向绑定等等。
对于hook,我们通常以useXXX开头,下面我们看一个简单的例子:
自定义修改title
useTitle:
import {useEffect,useRef} from "react"
function useTitle(title) {
useEffect(
() => {
document.title = title;
return () => (document.title = "主页");
},
[title]
);
}
export {useTitle}
test:
import React, { useState } from 'react'
import {useTitle} from "../hooks/index"
const Test = (props) => {
const [count,setCount] = useState(0);
const init = useRef(true);
useTitle("初始化页面");
return (
<>
<p>count:{count}</p>
<p onClick = {()=>{setCount(count+1)}}>chang Count</p>
</>
)
}
export default Test
下面再看一下双向绑定的例子:
useBind:
function useBind(val){
const [value,setValue] = useState(val);
const onChange = (e)=>setValue(e.target.value)
return {
value,
onChange
}
}
Test:
import {useBind} from "../hooks/index"
const GrandSon = (props) => {
const bindData = useBind("qwerr");
return (
<>
<p>{bindData.value}</p>
<input {...bindData} /> // 这一个有些同学可能会看不懂,其实就是 <input value={bindData.value} onChange={bindData.bindData} >
</>
)
}