React结合js快速理解学习
作者:秋了秋 发表时间:2023年06月12日
React是用javascript写的框架,本质是数据驱动,跟其它框架不同的是它是个”js和html混合体”,js里面一切皆对象,包括React也不另外,如果它长得不像对象,一定会有一个处理过程把它变成对象。
写react之前一定得引入框架代码:
<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <!-- 生产环境中不建议使用,这个文件就是编译jsx的,一般生产环境使用webpack来编译,不需要引入该文件,因为性能极差 --> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
本文为何要强调结合js来理解,也是为了区分js,否则写着写着react跟js搞混了忘记js怎么写了,比如以下代码:
const url = 'http://netblog.cn';
function H(obj) {
return (
<div>
<h1>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
<h3>{url}</h3>
</div>
)
}
ReactDOM.render(
<H name="秋了秋" />,
document.getElementById('example')
);
它的写法看起来是不伦不类,既有js又有html,这是它为了让开发者更直观的书写代码,直接写html,不至于写json这种长对象,所谓所见即所得, 对前端新人很友好,因为前端入门就是html。
这种写法放在js文件里面执行一定冒烟,它根本不是js语法,它是React专门定义的jsx文件里面的代码,React会将这种文件代码进行编译,编译成js,提取里面的html,把html转成若干属性的对象。再通过js对象构造真实的html结构,所以说jsx是虚拟DOM。
我们来剖析下为什么写这么不伦不类的代码:
function H(obj) {
return (
<div>
<h1>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
<h3>{url}</h3>
</div>
)
}
return 后面跟的是(),也可以写中括号[], 括号里面放的才是html代码,这在js里面应该是引号才合理:
function H(obj) {
return `
<div>
<h1>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
<h3>{url}</h3>
</div>`
}
Reac之所以用括号应该是刻意区分js的引号,他要兼容js的写法,能保证既可以写js也可以写jsx,如果碰到引号它就不解析和转化成对象,可以节省性能。
Tips: 括号可以省略,但是所有dom需要写在一行,一般是简短的可适用:
function H(obj) {
return <h1>Hello, world! {obj.name}</h1>;
}
需要注意组件只能包含一个顶层标签,否则会报错。也就是它只能返回一个节点,子节点不限,如果真的不需要外面的容器父节点怎么处理?可以写空标签:
function H(obj) {
return (
<>
<h1>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
<h3>{url}</h3>
</>
)
}
在jsx的虚拟dom中可以写表达式js代码和变量,通过大括号包住
function H(obj) {
return (
<div>
<h1 data-age= {obj.name}>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
</div>
)
}
这在js里面叫做封装了一个复用函数,在react里面叫复用组件,它可以通过ReactDOM渲染到页面上。
var myName = ‘秋了秋’;
ReactDOM.render(
<H name={myName } age=”18” />,
document.getElementById('example')
);
第二个参数即是容器对象,着重说第一个参数,标签名就是组件名字,属性就是传给组件的参数,跟以下代码是相同效果:
ReactDOM.render(
H({name: ‘秋了秋’, age: 18}),
document.getElementById('example')
);
这样更符合js的写法,因为属性可能是多个,所以它是个对象。
一切皆对象,所以对于样式的表达也不另外,
ReactDOM.render(
<H name="秋了秋" style={{width:’100px’,height:’100px’}} />,
document.getElementById('example')
);
这里为什么是双大括号,其实还是一个大括号,包的是一个对象{width:’100px’,height:’100px’},里面的括号是对象本身的...而且函数体内部style不能加引号
function H(obj) {
return (
<div>
<h1 style=”{obj.style}”>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
</div>
)
}
这样是会报错的,必须
function H(obj) {
return (
<div>
<h1 style={obj.style}>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
</div>
)
}
即便是其它属性也不能加引号,否则就会当字符串输出,不会编译:
function H(obj) {
return (
<div>
<h1 dd=”{obj.style}”>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
</div>
)
}
这样dd解释出来还是dd=”{obj.style}”
注意: 这里有一些跟原生html有些区分点:
class 属性变为 className
tabindex 属性变为 tabIndex
for 属性变为 htmlFor
textarea 的值通过需要通过 value 属性来指定
style 属性的值接收一个对象,css 的属性变为驼峰写法,如:backgroundColor。
上面是通过函数来创建的组件,如果是通过类创建的组件则可以通过this.props访问参数,前提是要继承 React.Component父类并且虚拟dom输出必须写在render方法内,相当于重写父类的render方法。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('example')
);
需要注意的是class 和 for 不能作为 XML 属性名。作为替代,React DOM 使用 className 和 htmlFor 来做对应的属性。
ReactDOM.render(
<Clock className=”netblog-cn” htmlFor=”xxx” />,
document.getElementById('example')
);
原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 HelloMessage 不能写成 helloMessage。
组件里面也可以包含组件:
function H(obj) {
return (
<div>
<h1 dd=”{obj.style}”>Hello, world! {obj.name}</h1>
<h2>现在是 {new Date().toLocaleTimeString()}.</h2>
</div>
)
}
function Div(obj) {
return (
<div class={obj.className}>
<H name=”秋了秋”>
</div>
)
}
前面说了react也是数据驱动,只需要更改数据就会重新渲染dom,那么并不是改任何数据都会渲染的,需要把数据挂在在state属性上,并且修改数据需要使用setState方法更改才行
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date(),
name: '秋了秋',
count: 0
};
}
changeData() {
this.setState({
count: this.state.count + 1,
});
}
render() {
return (
<div>
<h1>{this.state.name}{this.state.count}</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
const Instance = ReactDOM.render(
<Clock />,
document.getElementById('example')
);
setInterval(
() => {
Instance.changeData();
},
1000
)
在js里面类的实例化需要使用new Clock(),创建实例才能调用实例的方法,在jsx中 ReactDOM.render会返回实例化对象,挂载dom的时候就是实例化的时候。以上setInterval代码每秒钟调用实例方法changeData,changeData通过父组件的setState方法让state 对象的count 字段加一,触发了state数据的变更所以dom每秒都在重新渲染(调用组件里面的render函数)。
然而定时器这样写在dom移除的时候容易造成内存泄漏,我们可以监听父组件的componentDidMount(挂载时候的钩子)和componentWillUnmount(卸载时候的钩子)方法管理定时器
class Clock extends React.Component {
constructor(props) {
...
}
componentDidMount() {
this.timerID = setInterval(
() => this.changeData(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
changeData() {
...
}
render() {
...
}
}
关于绑定事件,react有个很烦人的坑需要特别注意:
class A extends React.Component {
handleClick() {
console.log('this is:', this);// undefined 这里写this指向的是undefined
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
要解决这个问题需要手动绑定this
class A extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this is:', this);// undefined 这里写this指向的是undefined
}
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
//或者
class A extends React.Component {
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
}
}
//或者
class A extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
return (
<button onClick={(e) => this.handleClick(e)}>Click me</button>
);
}
}
事件函数的传参
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
class A extends React.Component {
handleClick(param, e) {//注意第二个参数才是事件对象
console.log('this is:', param);
}
}
注意属性名不要使用key,跟class一样是系统保留关键字,key是用来标识元素的唯一身份,便于增删的时候识别到具体哪个dom。
const content = posts.map((post) =>
<Post key={post.id} title={post.title} />
);
Post 组件读不到props.key的,请使用其它属性名。
React的api用得最多的就是setState,想类似的还有replaceState,他们都是异步的,有回调函数setState是会把传入参数合并到原先的state数据,而replaceState是替换:
this.state = {
name: ‘秋了秋’,
age: 18
}
this.setState({
age: 16
});
最终数据是
this.state = {
name: ‘秋了秋’,
age: 16
}
而replaceState是替换,跟js逻辑差不多:
this.replaceState({
age: 16
}, function(){
console.log(‘可恶!我被别人减了两岁还把我的名字删了’);
});
最终数据是
this.state = {
age: 16
}
第一个参数也可以是个函数,函数的返回值作为最终设置的值
this.setState((prevState, props) => ({
age: prevState.age + props.age;
}));
相类似的api还有setProps,replaceProps,还有一个手动调用render的方法,即强制更新forceUpdate:
this.forceUpdate(function(){
console.log(‘我被强制更新了’);
});
除了这些api,还提供了一些钩子函数:
class Content extends React.Component {
componentWillMount() {
console.log('我在组件挂载之前执行!')
}
componentDidMount() {
console.log('我在组件挂载之后执行!')
}
componentWillReceiveProps(newProps) {
console.log('组件感受到即将prop更新但是还未更新,即将渲染之前调用!')
}
shouldComponentUpdate(newProps, newState) {
return true;//当 props 或 state 发生变化时,我会在渲染执行之前被调用。
}
componentWillUpdate(nextProps, nextState) {
console.log('组件更新之前调用!');
}
componentDidUpdate(prevProps, prevState) {
console.log('组件更新之后调用!')
}
componentWillUnmount() {
console.log('组件卸载之前调用!')
}
render() {
return (
<div>
<h3>{this.props.myNumber}</h3>
</div>
);
}
}
Jsx还有一个特殊的属性是ref,相当于js里面的选择器,所以在绑定普通属性的时候也要注意不要用ref,react是通过ref定位到组件里面的哪个dom元素。
class MyComponent extends React.Component {
handleClick() {
// 使用原生的 DOM API 获取焦点
this.refs.myInput.focus();
}
render() {
// 当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs
return (
<div>
<input type="text" ref="myInput" />
<input
type="button"
value="点我输入框获取焦点"
onClick={this.handleClick.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
以上钩子只在通过类来创建组件中可以用,那普通函数创建组件怎么办?react 16.8 新增了一些特性,hooks可以在函数组件里面使用。
1. useState通过调用简单的钩子函数就可以建立对某个数据监听,创建state。
function App () {
const [ count, setCount ] = useState(0);// 相当于类里面的this.state = {count: 0}
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
setCount 相当于类里面的this.setState函数。useState函数的参数为state的默认值,支持具有返回值的函数逻辑
const [ count, setCount ] = useState(function() {return 666*888;});
useState函数返回的是一个数组,数组第一项是state的值,第二个参数是设置state的函数,只有通过这个函数赖修改state才会触发组件更新,函数的第一个参数是组件上一个状态的state值:
setCount((count => count + 1);
useState使用的地方都可以用useReducer,他们有相似的功能,只是useReducer功能适用更复杂的state的更新,type有多种情况的时候,才会发挥useReducer的优势,否则,不如不用,其参数也不一样:
function App () {
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return {
...state,
score: state.score + action.payload
};
case "decrement":
return {
...state,
score: state.score - action.payload
};
default:
return state;
}
}
const [state, dispatch] = React.useReducer(reducer, {
name: "秋了秋",
score : 0
});
console.log(state);
return (
<div>
<button onClick={()=>{dispatch({type:"increment",payload:2})}}>Increment</button>
<button onClick={()=>{dispatch({type:"decrement",payload:2})}}>Decrement</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('test'));
useReducer第一个参数为dispatch传参的处理函数,第二个参数为state的默认值。
2. 使用useEffect函数赖监听任意数据:
const [name, setName] = useState('秋了秋');
const [age, setAge] = useState(18);
useEffect(function() {
alert('My name is ' + name + ', My age is ' + age);
}, [name,age])
第一个参数是数据变化时候执行的函数,当然useEffect不只有监听数据变化的功能,它的功能主要取决于第二个参数。
-
什么都不传,组件每次 render 之后 useEffect 都会调用,相当于类组件里面的componentDidMount 和 componentDidUpdate
-
传入一个空数组 [], 只会调用一次,相当于类组件里面的componentDidMount 和 componentWillUnmount
-
传入一个数组,其中包括变量,只有组件挂载时(相当于类组件里面的componentDidMount)和这些变量变动时,useEffect 才会执行
其实第二点和第三点属于一个点,主要看第三点,数组里面没有数据监听自然只会执行一次
function Test(data) {
const [name, setName] = React.useState('秋了秋');
const [age, setAge] = React.useState(18);
React.useEffect(function() {
console.log('My name is ' + name + ', My age is ' + (age + data.age));
}, [name, age]);
React.useEffect(function() {
console.log(1, 'http://netblog.cn');
}, []);
React.useEffect(function() {
console.log(2, 'http://netblog.cn');
});
const handleClick = function(e) {
setName('Bob');
setAge(20)
}
return (
<div onClick={handleClick}>
<div>My name is {name}</div>
<div>My age is {(age + data.age)}</div>
</div>
)
}
const addAge = -2;
ReactDOM.render(<Test age={addAge}/>, document.getElementById('example'));
useLayoutEffect跟useEffect相类似,只是useEffect是异步的要等所有渲染结束才执行,而useLayoutEffect是同步的,所以涉及到操作dom的代码建议使用useLayoutEffect,这样能及时访问到dom的当时状态,修改也只会造成一次回流。useEffect 的函数会在组件渲染到屏幕之后执行,此时对 DOM 进行修改,会触发浏览器再次进行回流、重绘,增加了性能上的损耗。
3. useContext的使用顾名思义就是创建作用域,在创建的作用域范围内都可以使用统一提供的参数,主要用来解决输出多个组件时候不需要多个组件传参的问题。
function Test1(obj) {
return (
<div>My name is {obj.name}1</div>
)
}
function Test2(obj) {
return (
<div>My name is {obj.name}2</div>
)
}
function Test3(obj) {
return (
<div>
<Test1 name=”秋了秋” />
<Test2 name=”秋了秋”/>
</div>
)
}
ReactDOM.render(<Test3 name={'秋了秋'}/>, document.getElementById('test'));
如果使用useContext:
const Context = React.createContext();
function Test1() {
const name = React.useContext(Context);
return (
<div>My name is {name}1</div>
)
}
function Test2() {
const name = React.useContext(Context);
return (
<div>My name is {name}2</div>
)
}
function Test3(obj) {
return (
<Context.Provider value={obj.name}>
<Test1/>
<Test2/>
</Context.Provider>
)
}
ReactDOM.render(<Test3 name={'秋了秋'}/>, document.getElementById('test'));
这里需要注意的是要先createContext,再用createContext出来的对象提供统一参数Context.Provider,在这个Provider包裹下的组件都可以使用它的参数value的值,跟js里面的闭包类似原理, 然后组件里面要调取参数就用useContext(Context)
4. useMemo和useEffect极其相似,只是useMemo通常用来定义一个函数,让这个函数只在监听数据变化的时候执行,纵使你在dom中调用这个函数多次,如果监听数据未变化,它将不再执行而是使用上一次的缓存数据。
跟useEffect的区别是useMemo是在dom渲染前执行,类比生命周期就是shouldComponentUpdate,useEffect在渲染后执行。useEffect函数里面可以操作dom,发请求这些,而useMemo不应该做这些。
useMemo相对useEffect起到了性能优化的作用。
function Test(obj) {
// 产品名称、价格
let [price, setPrice] = React.useState(1000);
let [name, setName] = React.useState('秋了秋');
let count = 0;
//如果不用useMemo每次渲染count都会被初始化未0,getCount 返回的值会在0和1不断跳动
const getCount = React.useMemo(function() {
console.log('执行获取count');
return ()=>++count;
}, [count]);
// 假设有一个业务函数 获取产品的名字
const getProductName = React.useMemo(function() {
console.log('getProductName触发');
name = name + '--netblog.cn'; return () => {
return name;
}
}, [name]);
//对比js的普通写法
const getProductName2 = function() {
console.log('getProductName触发');
name = name + '--netblog.cn';//如果不用useMemo,这个代码每次渲染都会执行造成不必要的性能消耗
return name;
}
return (
<React.Fragment>
<p>名称:{getProductName()}</p>
<p>价格:{price}¥</p>
<button onClick={() => setPrice(price+1)}>价钱+1</button>
<button onClick={() => setName((name) => {return name.replace(/\d/g, '') + getCount()})}>修改名字</button>
</React.Fragment>
)
}
ReactDOM.render(<Test />, document.getElementById('test'));
只有组件挂载和点“修改名字”的时候才会打印“getProductName触发”。
5. useCallback跟useMemo具有相同的效果,但是有些区别
useMemo用于缓存计算结果,确保只有在依赖项发生变化时才会重新计算。避免每次渲染都执行相同代码计算的性能损耗
useCallback用于缓存函数,确保只有在依赖项发生变化时才会重新创建函数。避免每次渲染创建一个新函数的开销。
如果需要经常使用某个函数,而这个函数的计算量很大,那么可以使用useMemo进行函数的缓存,而如果需要将该函数传递给子组件,那么可以使用useCallback进行函数的缓存。
function Test(obj) {
// 产品名称、价格
let [price, setPrice] = React.useState(1000);
//useCallback第一个参数为一个普通函数,useMemo是函数里面返回函数
const handleClick = React.useCallback(function() {
alert(price);
}, [price]);
return (
<React.Fragment>
<button onClick={handleClick}>查看价格</button>
<button onClick={()=>{setPrice(2000);alert('修改成功')}}>修改价格</button>
</React.Fragment>
)
}
ReactDOM.render(<Test />, document.getElementById('test'));
useMemo和useCallback本质都是用来优化性能的,属于react的打补丁函数,用来避免react的渲染机制导致的一些性能损耗问题。useMemo在有些时候也可以充当useEffect来使用,但是失去了设计它的初衷。
function Test(obj) {
// 产品名称、价格
let [price, setPrice] = React.useState(1000);
React.useEffect(function() {
alert('useEffect');
}, [price]);
React.useMemo(function() {
alert('useMemo');
}, [price]);
const handleClick = function() {
setPrice((prePrice) => prePrice + 1);
}
return <div>看看弹几次框,哪个框先弹的。价格:{price}<br/><button onClick={handleClick}>点我修改价格</button></div>;
}
ReactDOM.render(<Test />, document.getElementById('test'));
以上代码组件初始化的时候分别会弹useMemo、useEffect,修改价格的时候也会按这个顺序弹框,且弹useMemo的时候UI还没渲染出来,UI渲染完成后弹useEffect,只有触发时机不一样,效果都是同等的。
6. useRef是在函数组件里面是定义变量的,如果用js原生定义变量var myRef = undefined,组件每次渲染都会初始化一个变量,如何防止这种事情发生可以使用useState,但是useState创建的变量在不同渲染状态之间不共享,如果需要共享需要在外部创建全局变量,但是react提供了一个hack函数,就是使用let myRef = React.useRef(undefined);能在函数里面创建属于该组件独有的全局变量,同样是为了修正函数式编程组件做的hack,相当于类编程的this.myRef = undefined;
function App () {
let like = React.useRef(0);
function handleAlertClick() {
setTimeout(() => {
alert(`you clicked on ${like.current}`);
}, 3000);
}
return (
<div>
<button onClick={() => {like.current = like.current + 1;}}>{like.current}赞</button>
<button onClick={handleAlertClick}>Alert</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('test'));
修改和访问ref的值通过ref.current,更改ref创建的组件内的全局变量不会触发组件更新
上面在讲类组件的时候dom上可以放一个特殊的ref属性<button ref="myInput">netblog.cn</button>,这样就可以通过this.refs.myInput在类函数里面访问到这个dom,在函数式组件里面的hack方法是ref属性不绑定字符创而是绑定ref变量<button ref={like}>netblog.cn</button>,这样就可以通过like.current访问到该dom。
7. forwardRef 顾名思义是给ref辅助用的,意思是传递ref,把父组件的ref传到子组件里面去,这样子组件就能在html上绑定这个ref,绑定后父组件就能拿到这个ref绑定的dom。通常的如果不用forwardRef也能实现这样的功能:
function App () {
let input;
let [value, setValue] = React.useState('');
React.useEffect(()=>{
console.log(input);
}, []);
return [
<div key={0}>
<Child callback={(ref) => {
input = ref;
}} changeValue={(value)=>{setValue(value)}}/>
<div>{value}</div>
</div>
];
}
function Child(props) {
const ref = React.useRef();
React.useEffect(()=>{
props.callback(ref.current);
}, []);
const handleInput = React.useCallback(function() {
props.changeValue(ref.current.value);
}, [ref]);
return (
<div>
<input ref={ref} onInput={handleInput} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('test'));
但是使用forwardRef就不需要在dom里面写函数,直接写ref属性即可,一句话就是少些一些代码,逻辑更简单:
function App () {
let input = React.useRef();
let [value, setValue] = React.useState('');
React.useEffect(()=>{
console.log(input.current);
}, []);
return [
<div key={0}>
<Child ref={input} changeValue={(value)=>{setValue(value)}}/>
<div>{value}</div>
</div>
];
}
const Child = React.forwardRef((props, ref) => {
const handleInput = React.useCallback(function() {
props.changeValue(ref.current.value);
}, [ref]);
return (
<div>
<input ref={ref} onInput={handleInput} />
</div>
);
})
ReactDOM.render(<App />, document.getElementById('test'));
8. useImperativeHandle 是forwardRef 的补救函数,是子组件限制父组件对传递出去的ref的一些属性和方法的访问,如果不使用useImperativeHandle方法,则父组件可以对传递出来的ref任意方法进行使用,为了防止这种越权,可以明确暴露一些方法出去,只允许使用这些方法,并且方法可以子组件定义:
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
value: ()=>{
return '访问我要先交5毛钱';
}
}));
8. 自定义hook,即js里面的复用函数,只是这种函数必须用use开头命名,且函数内部必须使用了react内部hook的调用。
function App () {
const resetTimer = React.useRef(true);
const [age, setAge] = useSetAge(15, resetTimer);
const handleSetAge = React.useMemo(()=> {
return ()=> {
setAge(10);
resetTimer.current = true;
}
},[]);
return [
<div key={0}>
<div>实际年龄:{age}</div>
<button onClick={handleSetAge}>点击设置为10岁</button>
</div>
];
}
function useSetAge(defaultAge, resetTimer) {
const [age, setAge] = React.useState(defaultAge);
let timer = React.useRef(null);
if(age > 18) {
setAge(18);
if(timer.current) {
clearInterval(timer.current);
timer.current = null
}
console.error('永远18!');
}
React.useEffect(function() {
if(resetTimer.current) {
timer.current = setInterval(function() {
setAge(function(preAge) {
return preAge + 1;
});
}, 1000);
}
resetTimer.current = false;
});
return [age, setAge];
}
ReactDOM.render(<App />, document.getElementById('test'));