React基础学习

230 阅读13分钟

创建脚手架命令

推荐使用:npx create-react-app my-app

npm init react-app my-app

yarn create react-app my-app

脚手架使用React

1.导入react和react-dom

2.调用React.createElement()创建react元素

3.调用React.render()方法渲染 react 元素到页面中

// 导入react
import React from 'react';
import ReactDOM from 'react-dom';
// 创建
const title = React.createElement('h1', null, 'hello react')
// 渲染
ReactDOM.render(title, document.querySelector('#root'))

JSX基本学习

JSX的基本使用

React.createElement() 解决他的问题 可读差 看不出结构 JSX 就是JavaScript XML 优势:更加直观

提高了开发效率,降低了学习成本

import React from 'react';
import ReactDOM from 'react-dom';

// 使用JXS创建react元素
const title = <h1>hello react!</h1>
// 渲染
ReactDOM.render(title, document.querySelector('#root'))

注意点

1.特殊属性名:class -> className

2.属性名使用驼峰命名法

3.没有子节点的React元素可以用 /> 结束

4.推荐使用小括号包裹

使用JavaScript表达式注意点:

1.单大括号可以用JavaScript的任意表达式

2.JSX自身也是JS表达式

3.JS中的对象是一个例外,一般会出现在style属性中

4.不能使用语句

条件渲染

import React from 'react';
import ReactDOM from 'react-dom';

// 使用JXS创建react元素
const isLoading = true
const loadData = () => {
  if (isLoading) {
    return <div>loading...</div> // 还可写三元表达式 和 逻辑与运算符
  }
  return <div>数据加载完成此处显示加载后的数据</div>
}
const title = (
  <h1>
    条件渲染:
    {loadData()}
  </h1>
)
// 渲染
ReactDOM.render(title, document.querySelector('#root'))

列表渲染

import React from 'react';
import ReactDOM from 'react-dom';

// 使用JXS创建react元素
const songs = [
  { id: 1, name: '痴心绝对' },
  { id: 2, name: '江南' },
  { id: 3, name: '白羊' },
]

const list = (
  <ul>
    {songs.map(item => <li key={item.id}>{item.name}</li>)}
  </ul>
)


// 渲染
ReactDOM.render(list, document.querySelector('#root'))

注意:

1.使用map()

2.绑定key 哪个渲染绑定给谁 key最好使用唯一性

样式处理

行内样式style

const list = (
  <ul>
    {songs.map(item => <li style={{ color: 'red', background: 'skyblue' }} key={item.id}>{item.name}</li>)}
  </ul>
)

类名—className(推荐)

import './css/index'

const list = (
  <ul>
    {songs.map(item => <li className="title" style={{ color: 'red', background: 'skyblue' }} key={item.id}>{item.name}</li>)}
  </ul>
)

组件学习

函数组件

import React from 'react';
import ReactDOM from 'react-dom';


// 创建函数组件
function Hello() {
  return (
    <div>这是我的第一个函数组件</div>
  )
}

// 箭头函数
const Hello = () => <div>这是我的第一个箭头函数组件</div>

// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))

注意:

1.名字是大写开头

2.必须写返回值

类组件

import React from 'react';
import ReactDOM from 'react-dom';

// 使用类创建组件
class Hello extends React.Component {
  render() {
    return <div>Hello Class Components!</div>
  }
}

// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))

注意:

1.名字开头大写

2.必须继承React.Component

3.必须调用render()

4.必须要有返回值

抽离组件为独立的JS文件

1.创建一个Hello.js

import React from "react";

// 创建组件

class Hello extends React.Component {
    render() {
        return <div>第一个抽离的组件</div>
    }
}

export {
    Hello
}

2.在index中导入

import React from 'react';
import ReactDOM from 'react-dom';
// 导入Hello
import Hello from './Hello'



// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))

React事件处理

1.事件绑定

  • 语法:on+事件名称={事件处理程序};比如:onClick={()=>{}}
  • 注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * React 事件处理
 */

class App extends React.Component {
  handleClick() {
    console.log('触发了点击事件');
  }
  render() {
    return <button onClick={this.handleClick}>点我,点我</button>
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))
  • 函数组件绑定事件
import React from 'react';
import ReactDOM from 'react-dom';



function App() {
  function handleClick() {
    console.log('触发了点击按钮');
  }
  return (
    <button onClick={handleClick}>点我</button>
  )
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))

2.事件对象

  • 可以通过事件处理程序的参数获取到事件对象
  • React 中的事件对象叫做:合成事件(对象)
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
import React from 'react';
import ReactDOM from 'react-dom';



function App() {
  function handleClick(e) {
    // 阻止浏览器默认行为
    e.preventDefault()
    console.log('触发了点击按钮');
  }
  return (
    <a href="http://www.baidu.com" onClick={handleClick}>百度</a>
  )
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))

state的基本使用

import React from 'react';
import ReactDOM from 'react-dom';

/**
 * state的基本使用
 */
class App extends React.Component {
  // constructor() {
  //   super();
  //   this.state = {
  //     count: 0,
  //   };
  // }
  state = {
    count: 0,
  };

  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button
          onClick={() => {
            this.setState({
              count: this.state.count + 1,
            });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

注意:

1.处理this的值为undefined

  • 箭头函数
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * state的基本使用
 */
class App extends React.Component {
  // constructor() {
  //   super();
  //   this.state = {
  //     count: 0,
  //   };
  // }
  state = {
    count: 0,
  };
  onIncrement() {
    this.setState({
      count: this.state.count + 1,
    });
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        {/* <button
          onClick={() => {
            this.setState({
              count: this.state.count + 1,
            });
          }}
        >
          +1
        </button> */}
        <button onClick={() => this.onIncrement()}>+1</button>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

  • bind()
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * state的基本使用
 */
class App extends React.Component {
  constructor() {
    super();
    this.onIncrement = this.onIncrement.bind(this);
  }
  state = {
    count: 0,
  };
  onIncrement() {
    this.setState({
      count: this.state.count + 1,
    });
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.onIncrement}>+1</button>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

表单处理

1.受控组件

import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';

/**
 * state的基本使用
 */
class App extends React.Component {
  //   constructor() {
  //     super();
  //     this.onIncrement = this.onIncrement.bind(this);
  //   }
  state = {
    txt: '',
  };

  handleChange = (e) => {
    this.setState({
      txt: e.target.value,
    });
  };
  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.txt}
          onChange={this.handleChange}
        />
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

多表单元素优化步骤

  • 给表单添加name属性
  • 根据表单元素类型获取对应值
  • 在 change 事件处理程序中 通过 [name] 来修改对应的state
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';

/**
 * state的基本使用
 */
class App extends React.Component {
  state = {
    txt: '',
    city: 'sh',
    content: '',
    isChecked: false,
  };

  handleForm = (e) => {
    const target = e.target;

    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value,
    });
  };

  render() {
    return (
      <div>
        {/* 文本框 */}
        <input
          type="text"
          value={this.state.txt}
          name="txt"
          onChange={this.handleForm}
        />
        <br />
        {/* 富文本框 */}
        <textarea
          name="content"
          value={this.state.content}
          onChange={this.handleForm}
        ></textarea>
        <br />
        {/* 下拉框 */}
        <select value={this.state.city} name="city" onChange={this.handleForm}>
          <option value="sh">上海</option>
          <option value="bj">北京</option>
        </select>
        <br />
        {/* 多表单元素优化步骤 */}
        {/* <input
          type="text"
          value={this.state.txt}
          name="txt"
          onChange={this.handleForm}
        /> */}
        <br />
        {/* 复选框 */}
        <input
          type="checkbox"
          name="isChecked"
          value={this.state.isChecked}
          onChange={this.handleForm}
        />
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

2.非受控组件

  1. 调用react.creareRef() 方法创建一个ref对象
  2. 将创建好的 ref 对象添加 到文本框中
  3. 通过ref 对象获取到文本框的值
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';

/**
 * 非受控组件
 */
class App extends React.Component {
  constructor() {
    super();
    // 创建ref
    this.txtRef = React.createRef();
  }

  // 获取文本框的值
  getTxt = () => {
    console.log('文本框的值:', this.txtRef.current.value); // 获取当前的值
  };
  render() {
    return (
      <div>
        {/* 绑定ref */}
        <input type="text" ref={this.txtRef} />
        <button onClick={this.getTxt}>获取文本框的值</button>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

小案例练习

import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';

/**
 * 非受控组件
 */
class App extends React.Component {
  // 初始化状态
  state = {
    name: '',
    content: '',
    comments: [
      { id: 1, name: 'jack', content: '沙发!!!' },
      { id: 2, name: 'rose', content: '板凳' },
      { id: 3, name: 'tom', content: '123' },
    ],
  };

  // 处理表单元素的值
  handleForm = (e) => {
    const target = e.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value,
    });
  };
  // 发表评论
  comment = () => {
    const { name, content, comments } = this.state;
    if (name.trim().length === 0 || content.trim().length === 0) {
      alert('请输入完成信息哦~');

      this.setState({
        name: '',
        content: '',
      });
      return;
    }
    const newComments = [
      ...comments,
      {
        id: comments.length > 0 ? comments[comments.length - 1].id + 1 : 1,
        name,
        content,
      },
    ];
    // 返回一个新的数据设置上去
    this.setState({
      comments: newComments,
      name: '',
      content: '',
    });
  };
  //渲染评论列表
  renderList = () => {
    if (this.state.comments.length === 0) {
      return <div className="no-comment">暂无评论,快去评论吧</div>;
    }
    return (
      <ul>
        {this.state.comments.map((item) => (
          <li key={item.id}>
            <h3>评论人:{item.name}</h3>
            <p>评论内容:{item.content}</p>
          </li>
        ))}
      </ul>
    );
  };
  render() {
    return (
      <div className="app">
        <div>
          <input
            name="name"
            value={this.state.name}
            onChange={this.handleForm}
            type="text"
            placeholder="请输入评论人"
            id=""
          />
          <br />
          <textarea
            name="content"
            value={this.state.content}
            onChange={this.handleForm}
            cols="30"
            rows="10"
            placeholder="请输入评论内容"
          ></textarea>
          <br />
          <button onClick={this.comment}>发表评论</button>
        </div>
        {/* 通过条件渲染什么内容 */}

        {this.renderList()}
        {/* {this.state.comments.length === 0 ? (
          <div className="no-comment">暂无评论,快去评论吧</div>
        ) : (
          <ul>
            {this.state.comments.map((item) => (
              <li key={item.id}>
                <h3>评论人:{item.name}</h3>
                <p>评论内容:{item.content}</p>
              </li>
            ))}
          </ul>
        )} */}
      </div>
    );
  }
}

// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

组件进阶

组件通讯

默认情况下,组件是封闭的,一个完整的功能拆分为多个组件,更好的完成整个应用功能,多个组件共享某些数据,为了实现这些功能,就需要打破组件的封闭性,让其与外界沟通,这就是组件通讯

组件的props

  • 组件是封闭性的,要接收外部数据应该通过props 来实现
  • props的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

1.函数组件的props

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

const Hello = (props) => {
  console.log(props); // 是个对象
  return (
    <div>
      <h1>props: {props.name}</h1>
      <h1>年龄:{props.age}</h1>
    </div>
  );
};

// 渲染
ReactDOM.render(
  <Hello name="jack" age={19} />,
  document.querySelector('#root')
);

2.类组件props

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

class Hello extends React.Component {
  render() {
    console.log(this.props);
    return (
      <div>
        <h1>props:{this.props.name}</h1>
        <h1>年龄:{this.props.age}</h1>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(
  <Hello name="jack" age={19} />,
  document.querySelector('#root')
);

特点:

  • 只能读取

  • 可以传多个参数任意类型,是一个对象

  • 注意:使用类组件时,写了构造函数,应该将props 传递给 super(),否则,无法在构造函数中获取到props

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

class Hello extends React.Component {
  // 推荐使用 
  constructor(props) {
    super(props);
  }

  render() {
    return <div>接收到的数据:{this.props.age}</div>;
  }
}

// 渲染
ReactDOM.render(
  <Hello name="jack" age={19} />,
  document.querySelector('#root')
);

组件通讯的三种方式

1.父组件到子组件

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

class Parent extends React.Component {
  constructor(props) {
      super(props)
  }
  state = {
    lastName: '王',
  };

  render() {
    return (
      <div className="parent">
        父组件:
        <Child name={this.state.lastName} props={this.props} />
      </div>
    );
  }
}

const Child = (props) => {
  console.log(props);
  return (
    <div className="child">
      <p>子组件,接收到父组件的数据:{props.name}</p>
    </div>
  );
};

// 渲染
ReactDOM.render(
  <Parent name="jack" age={19} />,
  document.querySelector('#root')
);

2.子传父组件

利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

class Parent extends React.Component {
  state = {
    msg: '',
  };
  // 提供回调函数,用来接收数据
  getChilMsg = (data) => {
    console.log('接收到子度之间传递过来的数据:', data);
    this.setState({
      msg: data,
    });
  };

  render() {
    return (
      <div className="parent">
        父组件: {this.state.msg}
        <Child getMsg={this.getChilMsg} />
      </div>
    );
  }
}

class Child extends React.Component {
  state = {
    msg: 'hello',
  };
  //
  handleClick = () => {
    this.props.getMsg(this.state.msg);
  };

  render() {
    return (
      <div className="child">
        <p>
          子组件,接收到父组件的数据:{' '}
          <button onClick={this.handleClick}>点我给父组件传递数据</button>
        </p>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(
  <Parent name="jack" age={19} />,
  document.querySelector('#root')
);

3.兄弟组件通信

  • 将共享状态提升到公共父组件中,由公共组件管理这个状态
  • 思想:状态提升
  • 公共父组件职责:1.提供共享状态 2.提供操作方法
  • 要通讯的子组件只需通过 props 接收状态或操作状态的方法
import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

class Counter extends React.Component {
  state = {
    count: 1,
  };

  addCount = () => {
    const { count } = this.state;
    this.setState({
      count: count + 1,
    });
  };
  render() {
    return (
      <div>
        <Child1 count={this.state.count} />
        <Child2 count={this.state.count} addCount={this.addCount} />
      </div>
    );
  }
}

const Child1 = (props) => {
  return <h1>计数器:{props.count}</h1>;
};

const Child2 = (props) => {
  return <button onClick={() => props.addCount()}>+1</button>;
};
// 渲染
ReactDOM.render(<Counter />, document.querySelector('#root'));

Context

作用:跨组件传递函数

使用步骤:

1.调用React.createContext() 创建 Provider(提供数据)和Consumer(消费数据)两个组件

2.Provider 作为父节点

3.设置value属性,表示要传递的数据

import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

// 创建context得到两个组件
const { Provider, Consumer } = React.createContext();

class App extends React.Component {
  render() {
    return (
      <Provider value="pink">
        <div>
          <Child1 />
        </div>
      </Provider>
    );
  }
}

const Child1 = () => {
  return <Child2></Child2>;
};

const Child2 = () => {
  return (
    <div>
      <Consumer>{(data) => <span>{data}</span>}</Consumer>
      <h1>我是最里面的子组件</h1>
    </div>
  );
};
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

props深入

  • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件、函数)
import * as React from 'react';
import ReactDOM from 'react-dom';

/**
 * props
 */

const App = (props) => {
  console.log(props);
  return (
    <div>
      <h1>组件的子节点:{props.children}</h1>
    </div>
  );
};

// 渲染
ReactDOM.render(<App>我是子节点</App>, document.querySelector('#root'));

钩子函数

1.创建时

constructor -> render -> componentDidMount

import * as React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(props) {
    super(props);
    console.log('constructor'); // 初始化state this指向问题
    this.handleCLick = this.handleCLick.bind(this);
    this.state = {
      count: 0,
    };
  }
  componentDidMount() {
    const title = document.querySelector('#title');
    console.log(title);
    console.log('componentDidMount'); // 发送网络请求 DOM操作
  }

  handleCLick() {
    this.setState({
      count: this.state.count + 1,
    });
  }
  render() {
    console.log('render'); // 渲染UI 创建和更新时执行 (不能调用setState)
    return (
      <div>
        <h1 id="title">次数:{this.state.count} </h1>
        <button onClick={this.handleCLick}>打豆豆</button>
      </div>
    );
  }
}

// 渲染
ReactDOM.render(
  <App colors={['red', 'blue']} />,
  document.querySelector('#root')
);

2.更新时

触发的三种方式

  • 执行机制:1.setState() 2.forceUpdate() 3.接收新的props
  • render -> componentDidUpdate
import * as React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleCLick = this.handleCLick.bind(this);
    this.state = {
      count: 0,
    };
  }

  handleCLick() {
    this.setState({
      count: this.state.count + 1,
    });
    /**
     * 3.点击按钮触发 forceUpdate
     */
    this.forceUpdate();
  }
  // 更新阶段 触发的三种方式
  render() {
    /**
     * 1.调用setState
     */
    console.log('render');
    return (
      <div>
        <Counter count={this.state.count} />
        <button onClick={this.handleCLick}>打豆豆</button>
      </div>
    );
  }
}

class Counter extends React.Component {
  constructor(props) {
    super(props);
    console.log('constructor');
  }

  render() {
    /**
     * 2.调用newProps 接收新属性
     */
    console.log('子组件触发了render');
    const { count } = this.props;
    return <h1>次数:{count}</h1>;
  }
  componentDidUpdate(prevProps) {
    console.log('触发了componentDidMount', prevProps);
    /**
     * 注意:
     * 在这里使用setState  要加if
     * 比较前后的props 是否相同  prevProps 上一次的props  this.props 当前的props
     */
    if (prevProps.count !== this.props.count) {
      this.setState({});
    }
  }

// 渲染
ReactDOM.render(
  <App colors={['red', 'blue']} />,
  document.querySelector('#root')
);

3.卸载时

  • 执行机制:组件从页面消失
  • 做一些清理工作
import * as React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleCLick = this.handleCLick.bind(this);
    this.state = {
      count: 0,
    };
  }

  handleCLick() {
    this.setState({
      count: this.state.count + 1,
    });
    this.forceUpdate();
  }

  render() {
    console.log('render');
    return (
      <div>
        {this.state.count > 3 ? (
          <div>豆豆被打死了</div>
        ) : (
          <Counter count={this.state.count} />
        )}
        <button onClick={this.handleCLick}>打豆豆</button>
      </div>
    );
  }
}

class Counter extends React.Component {
  constructor(props) {
    super(props);
    console.log('constructor');
  }
  componentWillMount() {
    this.timerId = setInterval(() => {
      console.log('定时器执行');
    }, 500);
  }
  render() {
    const { count } = this.props;
    return <h1>次数:{count}</h1>;
  }

  componentWillUnmount() {
    console.warn('componentWillUnmount');

    // 清理定时器
    clearInterval(this.timerId);
  }
}

// 渲染
ReactDOM.render(
  <App colors={['red', 'blue']} />,
  document.querySelector('#root')
);

rnder-porps和高阶组件

  • 思考:如果两个组件中的部分功能相似或相同,该如何处理
  • 处理方式: 复用相似的功能(联想函数封装)
  • 复用什么?1. state 2. 操作state的方法 (组件状态逻辑)
  • 两种方式:1.render props 模式 2.高阶组件(HOC)
  • 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧。烟花而成的固定模式(写法)

render props 模式

  • 思路:将复用的state和操作state的方法封装到一个组件中

  • 问题1:如何拿到该组件中复用的state?

  • 在使用组件时,添加一个值为函数的props,通过 函数参数 来获取(需要组件内部实现)

  • 问题2:如何渲染任意的UI

  • 使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)

    使用步骤

  1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态2.操作方法)
  2. 将要复 用的状态作为 props.render(state)方法的参数,暴露到组件外部
  3. 使用props.render() 返回值作为要渲染的内容
import * as React from 'react';
import ReactDOM from 'react-dom';

class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0,
  };

  // 鼠标移动事件的事件处理程序
  handleMouseMove = (e) => {
    this.setState({
      x: e.clientX,
      y: e.clientY,
    });
  };
  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove);
  }

  render() {
    return this.props.render(this.state);
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props 模式</h1>
        <Mouse
          render={(mouse) => {
            return (
              <p>
                鼠标位置:{mouse.x} {mouse.y}
              </p>
            );
          }}
        />
      </div>
    );
  }
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

优化

import * as React from 'react';
import ReactDOM from 'react-dom';
import typeProps from 'prop-types';
import img from './images/logo192.png';
class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0,
  };

  // 鼠标移动事件的事件处理程序
  handleMouseMove = (e) => {
    this.setState({
      x: e.clientX,
      y: e.clientY,
    });
  };
  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove);
  }

  render() {
    return this.props.children(this.state);
  }
  // 移除手动事件
  componentWillUnmount() {
    window.removeEventListener('mousemove', this.handleMouseMove);
  }
}

Mouse.typeProps = {
  children: typeProps.func.isRequired,
};
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props 模式</h1>
        {/* <Mouse
          render={(mouse) => {
            return (
              <p>
                鼠标位置:{mouse.x} {mouse.y}
              </p>
            );
          }}
        /> */}
        <Mouse>
          {(mouse) => {
            return (
              <p>
                鼠标位置:{mouse.x} {mouse.y}
              </p>
            );
          }}
        </Mouse>
        {/* 跟随鼠标移动的图片 */}
        {/* <Mouse
          render={(mouse) => {
            return (
              <img
                src={img}
                alt="react"
                style={{
                  position: 'absolute',
                  left: mouse.x - 96,
                  top: mouse.y - 96,
                }}
              />
            );
          }}
        /> */}
        <Mouse>
          {(mouse) => (
            <img
              src={img}
              alt="react"
              style={{
                position: 'absolute',
                left: mouse.x - 96,
                top: mouse.y - 96,
              }}
            />
          )}
        </Mouse>
      </div>
    );
  }
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

高阶组件

  • 目的:实现状态逻辑复用
  • 采用包装模式,比如说:手机壳
  • 手机:获取保护功能
  • 手机壳:提供保护功能
  • 高阶组件就相当于手机壳,通过包装组件,增强组件功能

思路分析

  • 高阶组件是一个函数,接收要包装的组件,但会增加后的组件
  • 高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件的WrapperComponent

使用步骤

  1. 创建一个函数,名称约定以 with开头
  2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
import * as React from 'react';
import ReactDOM from 'react-dom';
import typeProps, { func } from 'prop-types';
import img from './images/logo192.png';

// 创建高阶组件
function withMouse(WrappedComponrnt) {
  // 该组件提供复用的状态逻辑
  class Mouse extends React.Component {
    state = {
      x: 0,
      y: 0,
    };
    // 鼠标移动事件的事件处理程序
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY,
      });
    };
    // 监听鼠标移动事件
    componentDidMount() {
      window.addEventListener('mousemove', this.handleMouseMove);
    }

    componentWillUnmount() {
      window.removeEventListener('mousemove', this.handleMouseMove);
    }

    render() {
      return <WrappedComponrnt {...this.state}></WrappedComponrnt>;
    }
  }

  return Mouse;
}

// 用来测试高阶组件
const Position = (props) => (
  <p>
    鼠标位置:{props.x} {props.y}
  </p>
);
// 获取增强后的组件
const MousePosition = withMouse(Position);
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 增强后的组件 */}
        <MousePosition></MousePosition>
      </div>
    );
  }
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

设置displayNama

  • 使用高阶组件存在的问题:得到的两个组件名称相同
  • 原因:默认情况下,React使用组件名称作为displayName
  • 解决方式:为高阶组件设置displayName 便于调试时区分不同的组件
  • displayName的作用:用于设置调试信息(React Developer Tools信息)
  • 设置方式:
  // 设置displayName
  Mouse.displayName = `WithMouse${getDisplayName(WrappedComponrnt)}`;

function getDisplayName(WrappedComponrnt) {
  return WrappedComponrnt.displayName || WrappedComponrnt.name || 'Component';
}

传递props

  • 问题:props丢失
  • 原因:高阶组件没有往下传递props
  • 解决方式:渲染WrappedComponent,将state 和 this.props 一起传递给组件
    render() {
      return <WrappedComponrnt {...this.state} {...this.props} />;
    }

完整代码

import * as React from 'react';
import ReactDOM from 'react-dom';
// import typeProps, { func } from 'prop-types';
// import img from './images/logo192.png';

// 创建高阶组件
function withMouse(WrappedComponrnt) {
  // 该组件提供复用的状态逻辑
  class Mouse extends React.Component {
    state = {
      x: 0,
      y: 0,
    };
    // 鼠标移动事件的事件处理程序
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY,
      });
    };
    // 监听鼠标移动事件
    componentDidMount() {
      window.addEventListener('mousemove', this.handleMouseMove);
    }

    componentWillUnmount() {
      window.removeEventListener('mousemove', this.handleMouseMove);
    }

    render() {
      return <WrappedComponrnt {...this.state} {...this.props} />;
    }
  }
  // 设置displayName
  Mouse.displayName = `WithMouse${getDisplayName(WrappedComponrnt)}`;
  return Mouse;
}

function getDisplayName(WrappedComponrnt) {
  return WrappedComponrnt.displayName || WrappedComponrnt.name || 'Component';
}

// 用来测试高阶组件
const Position = (props) => {
  console.log('props,', props);
  return (
    <p>
      鼠标位置:{props.x} {props.y}
    </p>
  );
};
// 获取增强后的组件
const MousePosition = withMouse(Position);
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 增强后的组件 */}
        <MousePosition a="1"></MousePosition>
      </div>
    );
  }
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));

总结

  1. 组件通讯是构建 react 应用必不可少
  2. props的灵活性让组件更加强大
  3. 状态提升是 react组件的常用模式
  4. 组件生命周期有助于理解组件的运行过程
  5. 钩子函数让开发者可以在特定的时机执行某些功能
  6. render props 模式 和 高阶组件都可以实现组件状态逻辑复用
  7. 组件极简模型:(state,props)=> UI