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 弹出项目真面目,会多出两个目录:

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

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

	// 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文件

	// .env
	PORT=8080

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

	// 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.js

	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.js

	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.js

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

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

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

条件语句可以基于上面结论实现,index.js

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

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

	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相关拓展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值