当前位置:首页 »“秋了秋”个人博客 » 前端编程 » React结合js快速理解学习

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不只有监听数据变化的功能,它的功能主要取决于第二个参数。

  1. 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于类组件里面的componentDidMount 和 componentDidUpdate

  2. 传入一个空数组 [], 只会调用一次,相当于类组件里面的componentDidMount 和 componentWillUnmount

  3. 传入一个数组,其中包括变量,只有组件挂载时(相当于类组件里面的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'));

useLayoutEffectuseEffect相类似,只是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. useMemouseEffect极其相似,只是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'));

0
文章作者: “秋了秋”个人博客,本站鼓励原创。
转载请注明本文地址:http://netblog.cn/blog/465.html
目录: 前端编程标签: React,js框架 903次阅读

请求播放音乐,请点击播放

登 录
点击获取验证码
还没账号?点击这里