react

172 阅读15分钟

react

1.jsx

  1. 在 JSX {} 中嵌入表达式,即可以嵌入任何有结果的东西,譬如变量、方法、组件标签等。
const name = 'bwf';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);
  1. JSX 也是一个表达式,也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>; 
  }
  return <h1>Hello, Stranger.</h1>;
}

3.JSX 中指定属性。

const element = <a href="https://www.reactjs.org"> link </a>;

const element = <img src={user.avatarUrl}></img>;

注意:

  • 在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。

  • 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

4.JSX 防止注入攻击

const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
  1. JSX 表示对象 Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

以下两种示例代码完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
  1. 用户定义的组件必须以大写字母开头,小写字母开头的元素代表一个 HTML 内置组件。
  2. 在运行时选择类型。
  3. if 语句以及 for 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用。但是,你可以用在 JSX 以外的代码中。比如:
  4. 属性展开
  5. 布尔类型、Null 以及 Undefined 将会忽略

2.元素渲染 和组件

React 元素是不可变对象

React组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome

2.1 函数组件与 class 组件

函数组件

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

class组件

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

2.2理解class组件中的this指向问题

见事件处理

看下这个简单需求吧:点击标题切换天气状态

class Weather extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isHot: true
         };
    }
    handleChangeWeather(){
       // const { isHot } = this.state;
       // this.setState({
       //   isHot: !isHot
       //})
        
        //官网是这样写的,当前state状态依赖于上一次的状态,setState建议传一个函数
         this.setState((prevState)=>({
            isHot:!prevState.isHot
        }))
    }

    render() {
        return (
            <div>
             {/*  
             h1标签的方式属于被实例调用,this指向Weather实例,但是还没有点击事件就会触发;
             不属于事件驱动,我们的需求是点击之后才改变state状态,并且会出现2大bug:
             1.react-dom.development.js:500 Warning:
             Cannot update during an existing state transition (such as within `render`).
             Render methods should be a pure function of props and state. 
             2.Maximum update depth exceeded. 
             */}
                <h1 onClick={this.handleChangeWeather()}>今天天气很 {this.state.isHot?'炎热':'凉爽'}</h1>
              {/*
              h2标签的方式属于事件驱动,由于函数是运行在class体内部的,所以this是undefined
              */}
                <h2 onClick={this.handleChangeWeather}>今天天气很 {this.state.isHot?'炎热':'凉爽'}</h2>
            </div>         
        )  
    }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(<Weather/>, domContainer);

由此可见上面2种方式都有问题,我们应该用onClick={this.handleChangeWeather}这种调用方式,需要做的就是解决this指向问题

方式一:在constructor中绑定this

 constructor(props) { 
    super(props);
    this.state = {
        isHot: true
     };
     // 1.constructor函数中的this指向组件实例对象
     // 2.bind返回的是一个函数,需要调用才会执行
    this.handleChangeWeather=this.handleChangeWeather.bind(this)
}

方式二:es6箭头函数

class Weather extends React.Component {
    constructor(props) { 
        super(props);
    }
    // state的简写方式 实例属性的新写法 这时,不需要在实例属性前面加上this。
    state = {
        isHot: true
    }
    
    // 注意: 这是 *实验性* class fields语法。
    handleChangeWeather = ()=>{
        // 箭头函数没有自己的this,顺着作用域链往外找
        const { isHot } = this.state;
        this.setState({
            isHot: !isHot
        })
    }
  
    render() {
        return (
            <div>
                <h2 onClick={this.handleChangeWeather}>今天天气很 {this.state.isHot?'炎热':'凉爽'}</h2>
            </div>         
        )  
    }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(<Weather/>, domContainer);

或者

 handleChangeWeather =function(){
        const { isHot } = this.state;
        this.setState({
            isHot: !isHot
        })
 }
 
<h2 onClick={()=>{this.handleChangeWeather()}}>今天天气很 {this.state.isHot?'炎热':'凉爽'}</h2>

上面这个语法问题在于每次渲染 Weather 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

3.样式

classnames

className的用法

//1.局部样式 ,相当于vue scoped
import styles from './Login.module.scss';

<div className={styles['form-wrapper']}></div>
<input type="text" className={styles.username} />

实际渲染可能是这样的
.Login_myform-wrapper__28oww {
   overflow-y: scroll;
}

//2.全局样式 (这样在别的页面如果也用到了login这个类,则都是一样的样式)
import  './Login.scss';
<h1 className ='login'>测试样式</h1>

//3.如果包含多个类,可以借助第三方库classnames
import cx from 'classnames';
<button onClick={btnClick} className={cx(style.btn ,style['primary-btn'])}>点击</button>

// 4动态样式

//一个类
<a className={active?style.active:''}>login页 </a>

//多个类
<a className={`${active?style.active:''} ${style['normal-link']}`}>login页 </a>

style的用法

const styleObj = {
    color:'green'
}
<h1 style={styleObj}>测试样式</h1>

// or
 <h1 style={{color:'red'}}>测试样式</h1>

4.事件处理

向事件处理程序传递参数

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

5.条件渲染

JavaScript 运算符 if

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}
function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {  
          return <UserGreeting />;  
      }  
      return <GuestGreeting />;
}
ReactDOM.render(
  <Greeting isLoggedIn={false} />, 
  document.getElementById('root')
 );

与运算符 &&

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 && 
          <h2> You have {unreadMessages.length} unread messages.  
          </h2> 
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

请注意,返回 false 的表达式会使 && 后面的元素被跳过,但会返回 false 表达式。在下面示例中,render 方法的返回值是 <div>0</div>

render() {
  const count = 0; 
  return (
        <div>
          { count && <h1>Messages: {count}</h1>}
        </div>
  );
}

三目运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. 
    </div>
  );
}

阻止组件渲染

若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

function WarningBanner(props) {
  if (!props.warn) {   
    return null;  
  }
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

元素变量

 render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
    button = <LogoutButton onClick={this.handleLogoutClick} />;  
    } else {
    button = <LoginButton onClick={this.handleLoginClick} />;
    }
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }

6.列表&key

  1. key 帮助 React 识别哪些元素改变了,比如被添加或删除。

  2. 一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key

  3. 当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key,但是如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。深度解析使用索引作为 key 的负面影响 深入解析为什么 key 是必须的

  4. 正确的使用 key 的方式:一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。

function ListItem(props) {
  // 正确!这里不需要指定 key: 
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定    
    <ListItem key={number.toString()} value={number} /> 
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
  1. key 值在兄弟节点之间必须唯一

数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值:

function Blog(props) {
 const sidebar = (
     <ul>
         {props.posts.map((post) =>
           <li key={post.id}>
             {post.title}
           </li>
         )}
       </ul>
 );
 const content = props.posts.map((post) => 
     <div key={post.id}>
         <h3>{post.title}</h3>
         <p>{post.content}</p>
       </div>
 );
 return (
   <div>
         {sidebar}
          <hr />
         {content}
   </div>
 );
}

const posts = [
 {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
 {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
 <Blog posts={posts} />,
 document.getElementById('root')
);
  1. key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值:
const content = posts.map((post) =>
  <Post
    key={post.id} 
    id={post.id}
    title={post.title} />
);

上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key

7.表单

受控组件

对于受控组件来说,输入的值始终由 React 的 state 驱动。

input textarea

value onChange

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) { 
      this.setState({value: event.target.value});
  }
  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
      <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
       </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

select

value onChange (select中绑定的value值代表哪个option被selected)

  <select value={this.state.value} onChange={this.handleChange}>
        <option value="grapefruit">葡萄柚</option>
        <option value="lime">酸橙</option>
        <option value="coconut">椰子</option>
        <option value="mango">芒果</option>
   </select>
   

下拉框实现多选,设置属性 multiple={true}

class OptionForm extends React.Component{
    constructor(props){
        super(props);
        this.state={
            selectedArr:[], //['grapefruit','lime']
            options: [
                { value: "grapefruit", label: "葡萄柚" },
                { value: "lime", label: "酸橙" },
                { value: "coconut", label: "椰子" },
                { value: "mango", label: "芒果" }
            ]
        }
        this.handleOptionChange = this.handleOptionChange.bind(this)
        this.handleSumbit = this.handleSumbit.bind(this)
    }
    handleOptionChange(event){
        // 找到被选中项的索引
        const index=this.state.selectedArr.findIndex(item=>item.value===event.target.value)
        if(index>=0){
            this.state.selectedArr.splice(index,1)
        }else{
            this.state.selectedArr.push(event.target.value)
        }
        // react的更新机制是通过Object.is()进行的浅比较,如果2次的值相同则不会更新,所以这里需要重新给数组
        const selectedArr = this.state.selectedArr;
        this.setState({
            selectedArr
        })
    }
    handleSumbit(event){
        event.preventDefault();
        alert(`selectedArr-${this.state.selectedArr}`)
        this.setState({
            selectedArr:[]
        })

    }
    render(){
        return (
            <form onSubmit={this.handleSumbit}>
                <select value={this.state.selectedArr} onChange={this.handleOptionChange} multiple={true}>
                    {this.state.options.map(option=>
                        <option 
                            value={option.value}
                            key={option.value}>
                            {option.label}
                        </option>)}
                </select>
                <input type="submit" value="提交" />
            </form>

        )
        
    }
}

处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。

class MyForm extends React.Component{
    constructor(props){
        super(props);
        this.state={
            username:'',
            password:'',
            isAgreed:false,
        }
        this.handleChange=this.handleChange.bind(this);
        this.handleSubmit=this.handleSubmit.bind(this);
    }
    handleChange(e){
        const target =  e.target
        const name = target.name;
        const valve = target.type === 'checkbox' ? target.checked : target.value
        this.setState({
            [name]:valve
        })
    }
    handleSubmit(event){
        event.preventDefault();
        // ...
        console.log('state',this.state)
    }
    render(){
        const {username,password,isAgreed}=this.state
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    用户名:<input type='text' name='username' value={username} onChange={this.handleChange}/>            
                </label>
                <br/>
                <label>
                    密码:<input type='text' name='password' value={password} onChange={this.handleChange}/>
                </label>
                <br/>
                <label>
                    同意:<input type='checkbox' name='isAgreed' checked={isAgreed} onChange={this.handleChange}/>
                </label>
                <br/>
                <input type="submit" value="提交" />
            </form>
        )
    }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(<MyForm/>, domContainer);

非受控组件

非受控组件将真实数据储存在 DOM 节点中,可以通过ref去获取dom节点中的内容

在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。在这种情况下, 你可以指定一个 defaultValue 属性,而不是 value

同样,<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked<select> 和 <textarea> 支持 defaultValue

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} /> 
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

文件输入 File API

在 React 中,<input type="file" /> 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。

class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`    );
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

ReactDOM.render(
  <FileInput />,
  document.getElementById('root')
);

如果你不清楚在某个场景应该使用受控组件还是非受控组件,可以阅读这篇关于受控和非受控输入组件的文章

8.组合vs继承

包含关系(通常应用于一些页面布局)

有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。

我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children} 
    </div>
  );
}

这使得别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
          <h1 className="Dialog-title">
              Welcome
          </h1>   
          <p className="Dialog-message"> 
             Thank you for visiting our spacecraft!     
          </p> 
    </FancyBorder>
  );
}

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}  
      </div>
      <div className="SplitPane-right">
        {props.right}     
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
        }
      right={
        <Chat /> 
        } />
  );
}

<Contacts /> 和 <Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。

特例关系(通常应用于一些公共组件)

有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog 可以说是 Dialog 的特殊实例。

在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}     
      </h1>
      <p className="Dialog-message">
        {props.message}    
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog      
     title="Welcome"   
     message="Thank you for visiting our spacecraft!" />  
   );
}

9 React哲学

帮助我们更好地去思考怎么设计开发好一个项目

第一步:将设计好的 UI 划分为组件层级

第二步:用 React 创建一个静态版本(其实就是我们所说的写静态页面。此时完全不应该使用 state 构建静态版本。state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。 当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。

第三步:确定 UI state 的最小(且完整)表示

第四步:确定 state 放置的位置

第五步:添加反向数据流(其实就是子组件如果需要更新父组件通过props传递的数据,此时父组件需要定义一个回调函数传递给子组件)

10 Hook

q1:什么是Hook?

Hook是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数

q2:为什么会在 React 中加入 Hook?动机

1 在组件之间复用状态逻辑很难。你也许会熟悉一些解决此类问题的方案,比如 render props 和 高阶组件。 Hook 使你在无需修改组件结构的情况下复用状态逻辑。

2 复杂组件变得难以理解。 每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。

3 难以理解的 class。 你必须去理解 JavaScript 中 this 的工作方式,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。

q3:Hook 使用规则?

1 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

2 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

q4:有哪些常用的hook?

github.com/tcatche/tca…

useState
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);

1.useState 和 setState都是异步更新?

2.class 中 state的更新会被合并 然而,不像 class 中的 this.setState,useState更新 state 变量总是替换它而不是合并它。

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});
function Box() {
  const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
  // ...
}
// ...
  useEffect(() => {
    function handleWindowMouseMove(e) {
     // 展开 「...state」 以确保我们没有 「丢失」 width 和 height  
      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));}
    // 注意:这是个简化版的实现
    window.addEventListener('mousemove', handleWindowMouseMove);
    return () => window.removeEventListener('mousemove', handleWindowMouseMove);
  }, []);
  // ...

然而,我们推荐把 state 切分成多个 state 变量,每个变量包含的不同值会在同时发生变化。 把独立的 state 变量拆分开还有另外的好处。这使得后期把一些相关的逻辑抽取到一个自定义 Hook 变得容易,比如说:

function Box() {
  const position = useWindowPosition();  
  const [size, setSize] = useState({ width: 100, height: 100 });
  // ...
}

function useWindowPosition() {  
const [position, setPosition] = useState({ left: 0, top: 0 });
  useEffect(() => {
    // ...
  }, []);
  return position;
}

函数式更新

function Counter({initialCount}) {
 const [count, setCount] = useState(initialCount);
 return (
   <>
     Count: {count}
     <button onClick={() => setCount(0)}>Reset</button>
     <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
     <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
   </>
 );
}

惰性初始 state 跳过 state 更新(React 使用 Object.is 比较算法 来比较 state。)

useEffect
function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {   
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {  
     function handleStatusChange(status) {
       setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

useEffect 每个 effect “属于”一次特定的渲染

1.无需清除的 effect

需要清除的 effect eg:订阅外部数据源 可以防止引起内存泄露

2.为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制

3.可以使用多个 effect。这会将不相关逻辑分离到不同的 effect 中

4.在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决,在useEffect中我们可以通过第二个参数添加依赖项,只有当依赖项发生变更时才会执行effect

5.useEffect的执行时机:React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便? useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行?fix 怎么理解?

6.effect 的条件执行:给 useEffect 传递第二个参数,是一个数组,请确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,

总结:

1.只要state发生变更,组件都会重渲染,都会执行effect,可能会导致性能问题

2.useState 和 setState都是异步更新? 在点击事件中拿不到state最新的值,但是在useEffect中可以拿到最新的值

useContext
useReducer

useState 的替代方案。当你有多个涉及子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常优于 useStateuseReducer 还允许你优化触发深度更新的组件的性能,因为你可以传递 dispatch 而不是使用回调

const[state, dispatch]=useReducer(reducer, initialArg, init);

reducer重写计数器案例

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

懒初始化

'use strict';

// 函数式组件
const {useState , useReducer} = React

function Counter({initialCount}){
    const counterReducer = (state,action)=>{
        switch(action.type){
            case 'increment' :
                // 踩坑记1 :state是一个对象,这样写,直接返回了一个值,无法拿到正取的值
                // return state.count+1;
                // 点第一次+ state ->1
                // 点第二次+ state.count +1 ->undefined +1 = NaN
                return {
                    count:state.count+1
                }
            case 'decrement' :
                return {
                    count:state.count-1
                }
            case 'reset':
                return init(action.payload);
            default:
                throw new Error();
                  
        }
    }
    const init=(initialCount)=>{
        return {
            count:initialCount
        }
    }
    // 重置时需要传递第三个参数,
    const [state,dispatch] = useReducer(counterReducer,initialCount,init)
    return (
        <div>
            Count: {state.count}
            <button onClick={() => dispatch({type:'reset',payload:initialCount})}>Reset</button>
            <button onClick={() => dispatch({type:'decrement'})}>-</button>
            <button onClick={() => dispatch({type:'increment'})}>+</button>
        </div>

    )
}

const domContainer = document.querySelector('#app');
ReactDOM.render(<Counter initialCount={0}/>, domContainer);
useCallback

返回一个 memoized 回调函数。可以配合React.memo()一起实现减少子组件的render次数

useMemo

返回一个 memoized 值。

比较useCallback 和 useMemo:useCallback 缓存钩子函数,useMemo 缓存返回值(计算结果)

type DependencyList = ReadonlyArray<any>;

function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
useRef

先来看下Refs吧,Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

回调 Refs
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = null;

    this.setInputRef= element => {
      this.inputRef = element;
    };

    this.focusInput = () => {
      // 使用原生 DOM API 使 text 输入框获得焦点
      if (this.inputRef) this.inputRef.focus();
    };
  }
  render() {
    // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    return (
      <div>
        <input
          type="text"
          ref={this.setInputRef}
        />
         {/* 或者 */}
        {/* <input ref={node => this.inputRef = node} type="text"  /> */}
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusInput}
        />
      </div>
    );
  }
} 

官网上测量dom节点的例子:

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {  
    if (node !== null) {    
      setHeight(node.getBoundingClientRect().height);   
      } 
   }, []);
  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>  
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

可复用hook

function MeasureExample() {
  const [rect, ref] = useClientRect();  
   return (
    <>
      <h1 ref={ref}>Hello, world</h1>
      {rect !== null &&
        <h2>The above header is {Math.round(rect.height)}px tall</h2>
      }
    </>
  );
}

function useClientRect() {
  const [rect, setRect] = useState(null);
  const ref = useCallback(node => {
    if (node !== null) {
      setRect(node.getBoundingClientRect());
    }
  }, []);
  return [rect, ref];
}

其实用useRef和useEffect也能实现啊

   const measuredRef = useRef(null);
    useEffect(() => {
      if(measuredRef!==null){
        console.log('measuredRef',measuredRef);
        setHeight(measuredRef.current.getBoundingClientRect().height);
      }
    }, [measuredRef]);
createRef
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.inputRef = React.createRef();   
    this.focusInput = this.focusInput.bind(this);
  }

  focusInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();
    }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `inputRef` 上
    return (
      <div>
        <input
          type="text"
          ref={this.inputRef} />       
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusInput}
        />
      </div>
    );
  }
}
函数式组件中用useRef
function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它  const textInput = useRef(null);
  function handleClick() {
    textInput.current.focus();  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

const FancyButton = React.forwardRef((props, ref) => ( 
   <button ref={ref} className="FancyButton">  
      {props.children}
    </button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值

useRef() Hook 不仅可以用于 DOM refs。「ref」 对象是一个 current 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。

function Timer() {
  const intervalRef = useRef();
  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}
  // ...
  function handleCancelClick() {
    clearInterval(intervalRef.current);  }
  // ...

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()

useLayoutEffect
useDebugValue

github.com/facebook/re… react.docschina.org/docs/hooks-… react.docschina.org/docs/state-…

setState的更新机制,怎么实现同步更新,写在setTimeout 和useEffect中有啥区别 react生态: react-router-dom react-redux