react+ react Hooks学习笔记

起步

  1. 安装官方脚手架: npm install -g create-react-app
  2. 建立项目: create-react-app react-study
  3. 启动项目: npm start

文件结构

文件结构一览

├── README.md           文档      
	├── public            静态资源
	│  ├── favicon.ico
	│  ├── index.html
	│  └── manifest.json
	└── src              源码
	 ├── App.css
	 ├── App.js 根组件
	 ├── App.test.js       
	 ├── index.css 全局样式
	 ├── index.js 入口文件
	 ├── logo.svg
	 └── serviceWorker.js     pwa支持
	├── package.json          npm 依赖

cra项目真容

使用 npm run eject 弹出项目真面目,会多出两个目录:javascript

├── config            
	  ├── env.js 处理.env环境变量配置文件
	  ├── paths.js 提供各类路径
	  ├── webpack.config.js webpack配置文件
	  └── webpackDevServer.config.js 测试服务器配置文件
	└── scripts  启动、打包和测试脚本        
	  ├── build.js 打包脚本、
	  ├── start.js 启动脚本
	  └── test.js 测试脚本

env.js用来处理.env文件中配置的环境变量css

// node运行环境:development、production、test等
	const NODE_ENV = process.env.NODE_ENV;
	// 要扫描的文件名数组
	var dotenvFiles = [
	 `${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local
	 `${paths.dotenv}.${NODE_ENV}`,    // .env.development
	 NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local
	 paths.dotenv, // .env
	].filter(Boolean);
	// 从.env*文件加载环境变量
	dotenvFiles.forEach(dotenvFile => {
	 if (fs.existsSync(dotenvFile)) {
	  require('dotenv-expand')(
	   require('dotenv').config({
	    path: dotenvFile,
	  })
	 );
	}
	});

实践一下,修改一下默认端口号,建立.env文件html

// .env
	PORT=8080

webpack.config.js 是webpack配置文件,开头的常量声明能够看出cra可以支持ts、sass及css模块化vue

// Check if TypeScript is setup
	const useTypeScript = fs.existsSync(paths.appTsConfig);
	// style files regexes
	const cssRegex = /\.css$/;
	const cssModuleRegex = /\.module\.css$/;
	const sassRegex = /\.(scss|sass)$/;
	const sassModuleRegex = /\.module\.(scss|sass)$/;

React和ReactDom

删除src下面全部代码,新建index.jsjava

import React from 'react';
	import ReactDOM from 'react-dom';
	ReactDOM.render(<h1>React666</h1>, document.querySelector('#root'));

React负责逻辑控制,数据 -> VDOM
ReactDom渲染实际DOM,VDOM -> DOM,若是换到移动端,就用别的库来渲染
React使用JSX来描述UI
入口文件定义,webpack.config.jsnode

entry: [
	// WebpackDevServer客户端,它实现开发时热更新功能
	isEnvDevelopment &&
	require.resolve('react-dev-utils/webpackHotDevClient'),
	// 应用程序入口:src/index
	paths.appIndexJs,
	].filter(Boolean),

JSX

表达式{}的使用,index.js
const name = "react study";
	const jsx = <h2>{name}</h2>;

函数也是合法表达式,index.jsreact

const user = { firstName: "tom", lastName: "jerry" };
	function formatName(user) {
	 return user.firstName + " " + user.lastName;
	}
	const jsx = <h2>{formatName(user)}</h2>;

jsx是js对象,也是合法表达式,index.jswebpack

const greet = <p>hello, Jerry</p>
	const jsx = <h2>{greet}</h2>;

条件语句能够基于上面结论实现,index.jsgit

const showTitle = true;
	const title = name ? <h2>{name}</h2> : null;
	const jsx = (
	 <div>
		 {/* 条件语句 */}
		 {title}
	 </div>
);

数组会被做为一组子元素对待,数组中存放一组jsx可用于显示列表数据github

const arr = [1,2,3].map(num => <li key={num}>{num}</li>)
	const jsx = (
	 <div>
	 	{/* 数组 */}
	  	<ul>{arr}</ul> 
	 </div>
);

属性的使用

import logo from "./logo.svg";
	const jsx = (
	 <div>
		 {/* 属性:静态值用双引号,动态值用花括号;class、for等要特殊处理。 */}
		  <img src={logo} style={{ width: 100 }} className="img" />
	 </div>
);

css模块化,建立index.module.css,index.js

import style from "./index.module.css";
	<img className={style.img} />

组件

组件的两种形式

class组件
class组件一般拥有状态和生命周期,继承于Component,实现render方法
components/JsxTest.js

import React, { Component } from "react";
	import logo from "../logo.svg";
	import style from "../index.module.css";
	export default class JsxTest extends Component {
	 render() {
	  const name = "react study";
	  const user = { firstName: "tom", lastName: "jerry" };
	  function formatName(user) {
	   return user.firstName + " " + user.lastName;
	 }
	  const greet = <p>hello, Jerry</p>;
	  const arr = [1, 2, 3].map(num => <li key={num}>{num}</li>);
	  return (
	   <div>
		   {/* 条件语句 */}
		   {name ? <h2>{name}</h2> : null}
		   {/* 函数也是表达式 */}
		   {formatName(user)}
		   {/* jsx也是表达式 */}
		   {greet}    
		   {/* 数组 */}
		    <ul>{arr}</ul>
		   {/* 属性 */}
		    <img src={logo} className={style.img} alt="" />
	   </div>
	 );
	}
  }

function组件

函数组件一般无状态,仅关注内容展现,返回渲染结果便可。
app.js

import React from "react";
	import JsxTest from "./components/JsxTest";
	function App() {
	 return (
	  <div>
	   <JsxTest />
	  </div>
	);
	}
	export default App;

组件状态管理

类组件中的状态管理

components/StateMgt.js
import React, { Component } from "react";
	export default function StateMgt {
	  return (
	    <div>
	      <Clock />
	    </div>
	 );
	}

建立一个Clock组件

class Clock extends React.Component {
	 constructor(props) {
	  super(props);
	  // 使用state属性维护状态,在构造函数中初始化状态
	  this.state = { date: new Date() };
	}
	 componentDidMount() {
	  // 组件挂载时启动定时器每秒更新状态
	  this.timerID = setInterval(() => {
	   // 使用setState方法更新状态
	   this.setState({
	    date: new Date()
	  });
	 }, 1000);
	}
	 componentWillUnmount() {
	  // 组件卸载时中止定时器
	  clearInterval(this.timerID);
	}
	 render() {
	  return <div>{this.state.date.toLocaleTimeString()}</div>;
	}
}

拓展:setState特性讨论

  • 用setState更新状态而不能直接修改
this.state.counter += 1; //错误的
  • setState是批量执行的,所以对同一个状态执行屡次只起一次做用,多个状态更新能够放在同一个setState中进行
componentDidMount() {
	 // 假如couter初始值为0,执行三次之后其结果是多少?
	 this.setState({counter: this.state.counter + 1});
	 this.setState({counter: this.state.counter + 1});
	 this.setState({counter: this.state.counter + 1});
}
  • setState一般是异步的,所以若是要获取到最新状态值有如下三种方式:
    1. 传递函数给setState方法
this.setState((state, props) => ({  counter: state.counter + 1}));// 1
	this.setState((state, props) => ({  counter: state.counter + 1}));// 2
	this.setState((state, props) => ({  counter: state.counter + 1}));// 3
2. 使用定时器:
setTimeout(() => {
	console.log(this.state.counter);
	}, 0);
  1. 原生事件中修改状态
componentDidMount(){
	  document.body.addEventListener('click', this.changeValue, false)
	}
	changeValue = () => {
	  this.setState({counter: this.state.counter+1})
	  console.log(this.state.counter)
	}

函数组件中的状态管理

import { useState, useEffect } from "react";
	function ClockFunc() {
	 // useState建立一个状态和修改该状态的函数
	 const [date, setDate] = useState(new Date());
	 // useEffect编写反作用代码(简单理解就是异步代码)
	 useEffect(() => {
	  // 启动定时器是咱们的反作用代码
	  const timerID = setInterval(() => {
	   setDate(new Date());
	 }, 1000);
	  // 返回清理函数
	  return () => clearInterval(timerID); // 至关于 组件卸载
	}, []);// 参数2传递空数组使咱们参数1函数仅执行一次
	 return <div>{date.toLocaleTimeString()}</div>;
	}

事件处理

React中使用onXX写法来监听事件。
范例:用户输入事件,建立EventHandle.js

import React, { Component } from "react";
	export default class EventHandle extends Component {
	 constructor(props) {
	  super(props);
	  this.state = {
	   name: ""
	 };
	  
	  this.handleChange = this.handleChange.bind(this);
	}
	 handleChange(e) {
	  this.setState({ name: e.target.value });
	}
	 render() {
	  return (
	   <div>
	   {/* 使用箭头函数,不须要指定回调函数this,且便于传递参数 */}
	   {/* <input type="text" value={this.state.name} onChange={e => this.handleChange(e)} /> */}
	   {/* 直接指定回调函数,须要指定其this指向,或者将回调设置为箭头函数属性 */}
	    <input
			type="text"
		    value={this.state.name}
		    onChange={this.handleChange}
	    />
	    <p>{this.state.name}</p>
	   </div>
	 );
   }
}

组件通讯

Props属性传递

Props属性传递可用于父子组件相互通讯
// index.js
	ReactDOM.render(<App title="开课吧真不错" />, document.querySelector('#root'));
	// App.js
	<h2>{this.props.title}</h2>

若是父组件传递的是函数,则能够把子组件信息传入父组件,这个常称为状态提高,StateMgt.js

// StateMgt
	<Clock change={this.onChange}/>
	// Clock
	this.timerID = setInterval(() => {
	  this.setState({
	    date: new Date()
	 }, ()=>{
	    // 每次状态更新就通知父组件
	    this.props.change(this.state.date);
	 });
	}, 1000);

context

跨层级组件之间通讯(与vue provide inject相似)

redux

相似vuex,无明显关系的组件间通讯

生命周期

React V16.3以前的生命周期
在这里插入图片描述
范例:验证生命周期,建立Lifecycle.js

import React, { Component } from "react";
	export default class Lifecycle extends Component {
	 constructor(props) {
	  super(props);
	  // 经常使用于初始化状态
	  console.log("1.组件构造函数执行");
	}
	 componentWillMount() {
	  // 此时能够访问状态和属性,可进行api调用等
	  console.log("2.组件将要挂载");
	}
	 componentDidMount() {
	  // 组件已挂载,可进行状态更新操做
	  console.log("3.组件已挂载");
	}
	 componentWillReceiveProps(nextProps, nextState) {
	  // 父组件传递的属性有变化,作相应响应
	  console.log("4.将要接收属性传递");
	}
	 shouldComponentUpdate(nextProps, nextState) {
	  // 组件是否须要更新,须要返回布尔值结果,优化点
	  console.log("5.组件是否须要更新?");
	  return true;
	}
	 componentWillUpdate() {
	  // 组件将要更新,可作更新统计
	  console.log("6.组件将要更新");
	}
	 componentDidUpdate() {
	  // 组件更新
	  console.log("7.组件已更新");
	}
	 componentWillUnmount() {
	  // 组件将要卸载, 可作清理工做
	  console.log("8.组件将要卸载");
	}
	 render() {
	  console.log("组件渲染");
	  return <div>生命周期探究</div>;
	}
 }

激活更新阶段:App.js

class App extends Component {
	 state = { prop: "some content" };
	 componentDidMount() {
	  this.setState({ prop: "new content" });
	}
	 render() {
	  return (
	   <div>
	    <Lifecycle prop={this.state.prop} />
	   </div>
	 );
	}
}

激活卸载阶段,App.js

class App extends Component {
	 state = { prop: "some content" };
	 componentDidMount() {
	  this.setState({ prop: "new content" });
	  setTimeout(() => {
	   this.setState({ prop: "" });
	 }, 2000);
	}
	 render() {
	  return (
	   <div>
	   {this.state.prop && <Lifecycle prop={this.state.prop} />}
	   </div>
	 );
   }
 }

生命周期探究

React v16.0前的生命周期

大部分团队不见得会跟进升到16版本,因此16前的生命周期仍是颇有必要掌握的,况且16也是基于以前的修改

在这里插入图片描述
第一个是组件初始化(initialization)阶段
也就是如下代码中类的构造方法( constructor() ),Test类继承了react Component这个基类,也就继承这个react的基类,才能有render(),生命周期等方法可使用,这也说明为何 函数组件不能使用这些方法 的缘由。
super(props) 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,功子组件读取(组件中props只读不可变,state可变)。 而 constructor() 用来作一些组件的初始化工做,如定义this.state的初始内容。

import React, { Component } from 'react';
	class Test extends Component {
		constructor(props) {
		super(props);
		}
	}

第二个是组件的挂载(Mounting)阶段
此阶段分为componentWillMount,render,componentDidMount三个时期。

  • componentWillMount:
    在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引发组件从新渲染,也能够把写在这边的内容提早到constructor()中,因此项目中不多用。

  • render:
    根据组件的props和state(无二者的重传递和重赋值,论值是否有变化,均可以引发组件从新render) ,return一个React元素(描述组件,即UI),不负责组件实际渲染工做,以后由React自身根据此元素去渲染出页面DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有反作用),不能在里面执行this.setState,会有改变组件状态的反作用。

  • componentDidMount:
    组件挂载到DOM后调用,且只会被调用一次
    第三个是组件的更新(update)阶段
    在讲述此阶段前须要先明确下react组件更新机制。setState引发的state更新或父组件从新render引发的props更新,更新后的state和props相对以前不管是否有变化,都将引发子组件的从新render。详细可看这篇文章
    形成组件更新有两类(三种)状况:

  • 1.父组件从新render
    父组件从新render引发子组件从新render的状况有两种
    a. 直接使用,每当父组件从新render致使的重传props,子组件将直接跟着从新渲染,不管props是否有变化。可经过shouldComponentUpdate方法优化。

class Child extends Component {
	shouldComponentUpdate(nextProps){ // 应该使用这个方法,不然不管props是否有变化都将会致使组件跟着从新渲染
	if(nextProps.someThings === this.props.someThings){
			return false
		}
	}
	render() {
			return <div>{this.props.someThings}</div>
		}
	}

b.在componentWillReceiveProps方法中,将props转换成本身的state

class Child extends Component {
	constructor(props) {
	super(props);
	this.state = {
		someThings: props.someThings
	};
 }
	componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法
		this.setState({someThings: nextProps.someThings});
	}
	render() {
		return <div>{this.state.someThings}</div>
	}
}

根据官网的描述
在该函数(componentWillReceiveProps)中调用 this.setState() 将不会引发第二次渲染。
是由于componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引发state变化,从而引发render,此时就不必再作第二次因重传props引发的render了,否则重复作同样的渲染了。

  • 2.组件自己调用setState,不管state有没有变化。可经过shouldComponentUpdate方法优化。
class Child extends Component {
	constructor(props) {
		super(props);
		this.state = {
			someThings:1
		}
   }
	shouldComponentUpdate(nextStates){ // 应该使用这个方法,不然不管state是否有变化都将会致使组件从新渲染
		if(nextStates.someThings === this.state.someThings){
			return false
		}
 	}
	handleClick = () => { // 虽然调用了setState ,但state并没有变化
		const preSomeThings = this.state.someThings
			this.setState({
			someThings: preSomeThings
		})
	}
	render() {
		return <div onClick = {this.handleClick}>{this.state.someThings}</div>
	}
 }

此阶段分为componentWillReceiveProps,shouldComponentUpdate,
componentWillUpdate,render,componentDidUpdate

  • componentWillReceiveProps(nextProps)
    此方法只调用于props引发的组件更新过程当中,参数nextProps是父组件传给当前组件的新props。但父组件render方法的调用不能保证重传给当前组件的props是有变化的,因此在此方法中根nextProps和this.props来查明重传的props是否改变,以及若是改变了要执行啥,好比根据新的props调用this.setState出发当前组件的从新render
  • shouldComponentUpdate(nextProps, nextState)
    此方法经过比较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新中止,以此可用来减小组件的没必要要渲染,优化组件性能。
    ps:这边也能够看出,就算componentWillReceiveProps()中执行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,否则nextState及当前组件的this.state的对比就一直是true了
  • componentWillUpdate(nextProps, nextState)
    此方法在调用render方法前执行,在这边可执行一些组件更新发生前的工做,通常较少用。
  • render
    render方法在上文讲过,这边只是从新调用。
  • componentDidUpdate(prevProps, prevState)
    此方法在组件更新后被调用,能够操做组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state

卸载阶段
此阶段只有一个生命周期方法:componentWillUnmount

  • componentWillUnmount
    此方法在组件被卸载前调用,能够在这里执行一些清理工做,好比清楚组件中使用的定时器,清楚
    componentDidMount中手动建立的DOM元素等,以免引发内存泄漏。

React v16.4 的生命周期

在这里插入图片描述
变动原因
原来(React v16.0前)的生命周期在React v16推出的Fiber以后就不合适了,由于若是要开启async rendering,在render函数以前的全部函数,都有可能被执行屡次。
原来(React v16.0前)的生命周期有哪些是在render前执行的呢?

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
    若是开发者开了async rendering,并且又在以上这些render前执行的生命周期方法作AJAX请求的话,那AJAX将被
    无谓地屡次调用。。。明显不是咱们指望的结果。并且在componentWillMount里发起AJAX,无论多快获得结果
    也赶不上首次render,并且componentWillMount在服务器端渲染也会被调用到(固然,也许这是预期的结
    果),这样的IO操做放在componentDidMount里更合适。
    禁止不能用比劝导开发者不要这样用的效果更好,因此除了shouldComponentUpdate,其余在render函数以前的
    全部函数(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被
    getDerivedStateFromProps替代。
    也就是用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在
    render以前只作无反作用的操做,并且能作的操做局限在根据props和state决定新的state
    React v16.0刚推出的时候,是增长了一个componentDidCatch生命周期函数,这只是一个增量式修改,彻底不影
    响原有生命周期函数;可是,到了React v16.3,大改动来了,引入了两个新的生命周期函数。

新引入了两个新的生命周期函数: getDerivedStateFromProps ,getSnapshotBeforeUpdate

getDerivedStateFromProps
static getDerivedStateFromProps(props, state) 在组件建立时和更新时的render方法以前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
getDerivedStateFromProps 原本(React v16.3中)是只在建立和更新(由父组件引起部分),也就是否是由父组件引起,那么getDerivedStateFromProps也不会被调用,如自身setState引起或者forceUpdate引起。
在这里插入图片描述
这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps不管是Mounting仍是
Updating,也不管是由于什么引发的Updating,所有都会被调用,具体可看React v16.4 的生命周期图。
getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() 被调用于render以后,能够读取但没法使用DOM的时候。它使您的组件能够在可
能更改以前从DOM捕获一些信息(例如滚动位置)。今生命周期返回的任何值都将做为参数传递给
componentDidUpdate()。

官网给的例子:

class ScrollingList extends React.Component {
	constructor(props) {
		super(props);
		this.listRef = React.createRef();
	}
	getSnapshotBeforeUpdate(prevProps, prevState) {
		//咱们是否要添加新的 items 到列表?
		// 捕捉滚动位置,以便咱们能够稍后调整滚动.
		if (prevProps.list.length < this.props.list.length) {
		const list = this.listRef.current;
		return list.scrollHeight - list.scrollTop;
	}
		return null;
}
	componentDidUpdate(prevProps, prevState, snapshot) {
		//若是咱们有snapshot值, 咱们已经添加了 新的items.
		// 调整滚动以致于这些新的items 不会将旧items推出视图。
		// (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
		if (snapshot !== null) {
		const list = this.listRef.current;
		list.scrollTop = list.scrollHeight - snapshot;
	}
}
	render() {
		return (
			<div ref={this.listRef}>{/* ...contents... */}</div>
		);
	}

React组件化

资源
context参考
HOC参考

组件跨层级通讯 - Context

范例:模拟redux存放全局状态,在组件间共享

import React from "react";
	// 建立上下文
	const Context = React.createContext();
	// 获取Provider和Consumer
	const Provider = Context.Provider;
	const Consumer = Context.Consumer;
	// Child显示计数器,并能修改它,多个Child之间须要共享数据
	function Child(props) {
	 return <div onClick={() => props.add()}>{props.counter}</div>;
	}
	export default class ContextTest extends React.Component {
		 // state是要传递的数据
		 state = {
		  counter: 0
		};
	 // add方法能够修改状态
		 add = () => {
		  this.setState(nextState => ({ counter: nextState.counter + 1 }));
		};
	 // counter状态变动
		 render() {
		  return (
			   <Provider value={{ counter: this.state.counter, add: this.add }}>
				   {/* Consumer中内嵌函数,其参数是传递的数据,返回要渲染的组件 */}
				   {/* 把value展开传递给Child */}
				    <Consumer>{value => <Child {...value} />}</Consumer>
				    <Consumer>{value => <Child {...value} />}</Consumer>
			   </Provider>
		    );
		}
	}

高阶组件

// Hoc.js
	import React from "react";
	// Lesson保证功能单一,它不关心数据来源,只负责显示
	function Lesson(props) {
	 return (
	  <div>
	  	{props.stage} - {props.title}
	  </div>
	);
	}
	// 模拟数据
	const lessons = [
		{ stage: "React", title: "核心API" },
		{ stage: "React", title: "组件化1" },
		{ stage: "React", title: "组件化2" }
	];
	// 高阶组件withContent负责包装传入组件Comp
	// 包装后组件可以根据传入索引获取课程数据,真实案例中能够经过api查询获得
	const withContent = Comp => props => {
		 const content = lessons[props.idx];
		 // {...props}将属性展开传递下去
		 return <Comp {...content} />;
	};
	// LessonWithContent是包装后的组件
	const LessonWithContent = withContent(Lesson);
	export default function HocTest() {
	 // HocTest渲染三个LessonWithContent组件
	 return (
	  <div>
	  {[0,0,0].map((item, idx) => (
	    <LessonWithContent idx={idx} key={idx} />
	  ))}
	  </div>
	);
	}

范例:改造前面案例使上下文使用更优雅

// withConsumer是高阶组件工厂,它能根据配置返回一个高阶组件
	function withConsumer(Consumer) {
	 return Comp => props => {
	  return <Consumer>{value => <Comp {...value} {...props} />}</Consumer>;
	};
	}
	// Child显示计数器,并能修改它,多个Child之间须要共享数据
	// 新的Child是经过withConsumer(Consumer)返回的高阶组件包装所得
	const Child = withConsumer(Consumer)(function (props) {
	 return <div onClick={() => props.add()} title={props.name}>{props.counter}</div>;
	});
	export default class ContextTest extends React.Component {
	 render() {
	  return (
	   <Provider value={{ counter: this.state.counter, add: this.add }}>
		   {/* 改造过的Child能够自动从Consumer获取值,直接用就行了 */}
		    <Child name="foo"/>
		    <Child name="bar"/>
	   </Provider>
	 );
	}
}

链式调用

// 高阶组件withLog负责包装传入组件Comp
	// 包装后组件在挂载时能够输出日志记录
	const withLog = Comp => {
	 // 返回组件须要生命周期,所以声明为class组件
	 return class extends React.Component {
	  render() {
	   return <Comp {...this.props} />;
	 }
	  componentDidMount() {
	   console.log("didMount", this.props);
	 }
   };
};
	// LessonWithContent是包装后的组件
	const LessonWithContent = withLog(withContent(Lesson));

装饰器写法

// 装饰器只能用在class上
	// 执行顺序从下往上
	@withLog
	@withContent
	class Lesson2 extends React.Component {
	 render() {
	  return (
	   <div>
	   {this.props.stage} - {this.props.title}
	   </div>
	 );
	}
	}
	export default function HocTest() {
	 // 这里使用Lesson2
	 return (
	  <div>
	  {[0, 0, 0].map((item, idx) => (
	    <Lesson2 idx={idx} key={idx} />
	  ))}
	  </div>
	);
	}
// 注意修改App.js中引入部分,添加一个后缀名
// 要求cra版本高于2.1.0

组件复合 - Composition

复合组件给与你足够的敏捷去定义自定义组件的外观和行为
组件复合
范例:Dialog组件负责展现,内容从外部传入便可,components/Composition.js

import React from "react";
	// Dialog定义组件外观和行为
	function Dialog(props) {
	 return <div style={{ border: "1px solid blue" }}>{props.children}</div>;
	}
	export default function Composition() {
	return (
	  <div>
	  {/* 传入显示内容 */}
	   <Dialog>
	    <h1>组件复合</h1>
	    <p>复合组件给与你足够的敏捷去定义自定义组件的外观和行为</p>
	   </Dialog>
	  </div>
	);
 }

范例:传个对象进去,key表示具名插槽

import React from "react";
	// 获取相应部份内容展现在指定位置
	function Dialog(props) {
	 return (
	  <div style={{ border: "1px solid blue" }}>
	  {props.children.default}
	   <div>{props.children.footer}</div>
	  </div>
	);
	}
	export default function Composition() {
	 return (
	  <div>
	  {/* 传入显示内容 */}
	   <Dialog>
	   {{
	     default: (
	      <>
	       <h1>组件复合</h1>
	       <p>复合组件给与你足够的敏捷去定义自定义组件的外观和行为</p>
	      </>
	    ),
	     footer: <button onClick={() => alert("react确实好")}>肯定</button>
	   }}
	   </Dialog>
	  </div>
	);
 }

若是传入的是函数,还能够实现做用域插槽的功能

function Dialog(props) {
	// 备选消息
	 const messages = {
	   "foo": {title: 'foo', content: 'foo~'},
	   "bar": {title: 'bar', content: 'bar~'},
	}
	 // 执行函数得到要显示的内容
	 const {body, footer} = props.children(messages[props.msg]);
	 return (
	  <div style={{ border: "1px solid blue" }}>
	  {/* 此处显示的内容是动态生成的 */}
	  {body}
	   <div>{footer}</div>
	  </div>
	);
	}
	export default function Composition() {
	 return (
	  <div>
	  {/* 执行显示消息的key */}
	   <Dialog msg="foo">
	   {/* 修改成函数形式,根据传入值生成最终内容 */}
	   {({title, content}) => ({
	     body: (
	      <>
	       <h1>{title}</h1>
	       <p>{content}</p>
	      </>
	    ),
	     footer: <button onClick={() => alert("react确实好")}>肯定</button>
	   })}
	   </Dialog>
	  </div>
	);
 }

若是props.children是jsx,此时它是不能修改的
范例:实现RadioGroup和Radio组件,可经过RadioGroup设置Radio的name

function RadioGroup(props) {
	 // 不可行,
	 // React.Children.forEach(props.children, child => {
	 // child.props.name = props.name;
	 // });
	 return (
	  <div>
	  {React.Children.map(props.children, child => {
	    // 要修改child属性必须先克隆它
	    return React.cloneElement(child, { name: props.name });
	    })}
	  </div>
	);
	}
	// Radio传入value,name和children,注意区分
	function Radio({ children, ...rest }) {
	 return (
	  <label>
	   <input type="radio" {...rest} />
	  {children}
	  </label>
	);
	}
	export default function Composition() {
	 return (
	  <div>
	  {/* 执行显示消息的key */}
	   <RadioGroup name="mvvm">
	    <Radio value="vue">vue</Radio>
	    <Radio value="react">react</Radio>
	    <Radio value="ng">angular</Radio>
	   </RadioGroup>
	  </div>
	);
 }

Hooks

状态钩子 State Hook

  • 建立HooksTest.js
import React, { useState } from "react";
	export default function HooksTest() {
	 // useState(initialState),接收初始状态,返回一个由状态和其更新函数组成的数组
	 const [fruit, setFruit] = useState("");
	 return (
	  <div>
	   <p>{fruit === "" ? "请选择喜好的水果:" : `您的选择是:${fruit}`}</p>
	  </div>
	);
 }
  • 声明多个状态变量
// 声明列表组件
	function FruitList({fruits, onSetFruit}) {
	 return (
	  <ul>
	  {fruits.map(f => (
	    <li key={f} onClick={() => onSetFruit(f)}>
	    {f}
	    </li>
	  ))}
	  </ul>
	);
	}
	export default function HooksTest() {
	 // 声明数组状态
	 const [fruits, setFruits] = useState(["香蕉", "西瓜"]);
	 return (
	  <div>
	  {/*添加列表组件*/}
	   <FruitList fruits={fruits} onSetFruit={setFruit}/>
	  </div>
	);
	}
  • 用户输入处理
// 声明输入组件
	function FruitAdd(props) {
	 // 输入内容状态及设置内容状态的方法
	 const [pname, setPname] = useState("");
	 // 键盘事件处理
	 const onAddFruit = e => {
	  if (e.key === "Enter") {
	   props.onAddFruit(pname);
	   setPname("");
	 }
	 };
	 return (
	  <div>
	   <input
	    type="text"
	    value={pname}
	    onChange={e => setPname(e.target.value)}
	    onKeyDown={onAddFruit}
	   />
	  </div>
	);
	}
	export default function HooksTest() {
	 // ...
	 return (
	  <div>
	  {/*添加水果组件*/}
	   <FruitAdd onAddFruit={pname => setFruits([...fruits, pname])} />
	  </div>
	);
	}

反作用钩子 Effect Hook

useEffect 给函数组件增长了执行反作用操做的能力。
反作用(Side Effect)是指一个 function 作了和自己运算返回值无关的事,好比:修改了全局变量、修改了传入的参数、甚至是 console.log(),因此 ajax 操做,修改 dom 都是算做反作用。

  • 异步数据获取,更新HooksTest.js
import { useEffect } from "react";
	useEffect(()=>{
	  setTimeout(() => {
	    setFruits(['香蕉','西瓜'])
	 }, 1000);
	},[])// 设置空数组意为没有依赖,则反作用操做仅执行一次

若是反作用操做对某状态有依赖,务必添加依赖选项

useEffect(() => {
	document.title = fruit;
	}, [fruit]);
  • 清除工做:有一些反作用是须要清除的,清除工做很是重要的,能够防止引发内存泄露
useEffect(() => {
	 const timer = setInterval(() => {
	   console.log('msg');    
	 }, 1000);
	 return function(){
	   clearInterval(timer);
	 }
	}, []);

useReducer
useReducer是useState的可选项,经常使用于组件有复杂状态逻辑时,相似于redux中reducer概念。

  • 商品列表状态维护
import { useReducer } from "react";
	// 添加fruit状态维护fruitReducer
	function fruitReducer(state, action) {
	 switch (action.type) {
	  case "init":
	   return action.payload;
	  case "add":
	   return [...state, action.payload];
	  default:
	   return state;
	}
	}
	export default function HooksTest() {
	 // 组件内的状态不须要了
	 // const [fruits, setFruits] = useState([]);
	 // useReducer(reducer,initState)
	 const [fruits, dispatch] = useReducer(fruitReducer, []);
	 useEffect(() => {
	  setTimeout(() => {
	   // setFruits(["香蕉", "西瓜"]);
	   // 变动状态,派发动做便可
	   dispatch({ type: "init", payload: ["香蕉", "西瓜"] });
	 }, 1000);
	}, []);
	 return (
	  <div>
	  {/*此处修改成派发动做*/}
	   <FruitAdd onAddFruit={pname => dispatch({type: 'add', payload: pname})} />
	  </div>
	);
}

useContext
useContext用于在快速在函数组件中导入上下文。

import React, { useContext } from "react";
	// 建立上下文
	const Context = React.createContext();
	export default function HooksTest() {
	 // ...
	 return (
	 {/* 提供上下文的值 */}
	  <Context.Provider value={{fruits,dispatch}}>
	   <div>
	   {/* 这里再也不须要给FruitAdd传递状态mutation函数,实现了解耦 */}
	    <FruitAdd />
	   </div>
	  </Context.Provider>
	);
	}
	function FruitAdd(props) {
	 // 使用useContext获取上下文
	 const {dispatch} = useContext(Context)
	 const onAddFruit = e => {
	  if (e.key === "Enter") {
	   // 直接派发动做修改状态
	   dispatch({ type: "add", payload: pname })
	   setPname("");
	 }
	};
	 // ...
	}

Hooks相关拓展