React基础知识

React简介

React是一个声明式、高效且灵活的用于构建用户界面的JavaScript库。使用React可以将一些简短的、独立的代码片段组合成复杂的UI界面,这些代码片段称为组件
UI = render(data) => 单向数据流

MVC、MVVM

MVC在这里插入图片描述
在Model中定义数据及其改变数据的方法,使用观察者模式定义注册所有View的方法;在Controller中初始化View和Model,调用Model的方法让View向Model注册,这样Model更新就会去通知View,从而更新视图;在View中根据对应DOM绑定方法,当用户操作DOM时,操作Controller中的更新Model的方法。

MVVM在这里插入图片描述
在Model中只定义数据,在ViewModel中定义操作数据的方法,当用户操作View时,View调用ViewModel中操作数据的方法,从而改变了Model,ViewModel中的数据发生变化也通知View更新视图,即实现了View和ViewModel的双向数据绑定。

其实Vue和React严格来说都不是MVVM模式。因为Vue的ref属性直接操作了DOM,跳过了ViewModel;而React只是单向数据流,即只有视图是根据数据变化的。

JSX模板语法

JSX称为JS的语法扩展,将UI与逻辑层耦合在组件⾥,⽤{}标识。因为 JSX 语法上更接近 JS ⽽不是 HTML,所以使⽤ camelCase(小驼峰命名)来定义属性的名称; JSX ⾥的 class 变成了 className,而 tabindex 则变为 tabIndex。

  1. JSX支持JS表达式、变量、方法名
//变量
const name = 'casey';
const element = <h1>hello, {name}</h1>

//方法
const user = {
	firstName: 'wa',
	lastName: 'Rui'
};
function formarName(user) {
	return user.firstName + ' ' + user.lastName;
};
const element = (
	<h1>hello, {formatName(user)}</h1>
);
  1. JSX指定属性。(支持防注入,可预防XSS攻击。)React如何预防XSS(反射型XSS、存储型XSS):在渲染到浏览器前进行转义,对含有特殊含义的字符都转义了,恶意代码在渲染到HTML前都被转成了字符串。
  2. JSX表示对象:
const element = (
	<h1 className='a'>haha</h1>
);
//等同于
const element = React.createElement('h1', { className:'a' }, 'haha');
const element = {
	type: 'h1',
	props: {
		className: 'a',
		children: 'haha'
	}
}
  1. 将JSX渲染为DOM:
ReactDOM.render(element, document.getElementById('root');
//render只能代表当前时刻的状态;更新元素只能再次ReactDOM.render
  1. JSX转JS:babel。

props及state

组件

从概念上类似于JavaScript函数,接收任意的入参,即props,并返回用于描述页面展示内容的React元素。分为函数式组件和Class类组件

//函数式组件
function Welcome(props) {
	return <h1>hello, {props.name}</h1>;
}
//类组件
class Welcome extends React.Component {
	render() {
		return <h1>hello, {this.props.name}</h1>
	}
}
//自定义组件使用大写字母开头

//渲染组件
ReactDOM.render(element, document.getElementById('root'));

组件的组合与拆分:

//页面内多次引用
<div>
	<Welcome name='casey'/>
	<Welcome name='jom'/>
</div>

//组合
function Comment(props) {
	return (
		<div className='Comment'>
			<div className='UserInfo'>
				<img className='Avatar' src={props.author.avatarUrl} alt={props.author.name}/>
				<div className='UserInfo-name'>
					{props.author.name}
				</div>
			</div>
			<div className='Comment-text'>
				{props.text}
			</div>
			<div className='Comment-date'>
				{formatDate(props.date)}
			</div>
		</div>
	)
}
//拆分
function Comment(props) {
	return (
		<div className='Comment'>
			<UserInfo user={props.author}/>
			<div className='Comment-text'>
				{props.text}
			</div>
			<div className='Comment-date'>
				{formatDate(props.date)}
			</div>
		</div>
	)
}
props

所有React组件都必须像纯函数一样保护它们的props不被更改

//使用props形式
function Clock(props) {
	return (
		<div>
			<h1>hello, world</h1>
			<h2>it is {props.date.toLocaleTimeString()}</h2>
		</div>
	);
}
function tick() {
	ReactDOM.render(
		<Clock date={new Date()}/>,
		document.getElementById('root')
	);
}
setInterval(tick, 1000);
state

如何避免多次ReactDom.render?

//引用生命周期,根组件保留一个
class Clock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			date: new Date()
		}
	}
	componentDidMount() {
		this.timeID = setInterval(() => this.tick(), 1000);
	}
	componentWillUnmount() {
		clearInterval(this.timeID);
	}
	tick() {
		this.setState({
			date: new Date()
		});
	}
	render() {
		return (
			<div>
				<h1>hello, world</h1>
				<h2>it is {this.state.date.toLocaleTimeString()}</h2>
			</div>
		)
	}
}
ReactDOM.render(<Clock/>, document.getElementById('root'));

注意事项:

  1. 构造函数是唯一可以给state赋值的地方
  2. state更新可能是异步的,可能是同步的
//异步更新
this.setState({
	counter: this.state.counter + this.props.increment
});
//同步更新
this.setState(function(state, props) {
	return {
		counter: state.counter + props.increment
	};
});
  1. state更新会合并
  2. 单向数据流state只在当前组件里生效,属于组件内的属性。重复实例化相同的组件,内部的内存地址也是不一样的。
setState

异步目的:批量处理,性能优化。

setState异步情况:
//1.合成事件
class App extends Component {
	state = { val: 0 };
	increment = () => {
		this.setState({ val: this.state.val + 1 });
		console.log(this.state.val);//0
	}
	render() {
		return (
			<div onClick={this.increment}>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
//2.生命周期
class App extends Component {
	state = { val: 0 };
	componentDidMount() {
		this.setState({ val: this.state.val + 1 });
		console.log(this.state.val);//0
	}
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

同步更新情况:
//1.原生事件
class App extends Component {
	state = { val: 0 };
	changeValue = () => {
		this.setState({ val: this.state.val + 1 });
		console.log(this.state.val);//1
	}
	componentDidMount() {
		document.body.addEventListener('click', this.changeValue, false);
	}
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}
//2.setTimeout
class App extends Component {
	state = { val: 0 };
	componentDidMount() {
		setTimeout(_ => {
			this.setState({ val: this.state.val + 1 });
			console.log(this.state.val);//1
		}, 0);
	}
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

//批处理(batch处理)
class App extends Component {
	state = { val: 0 };
	batchUpdates = () => {
		this.setState({ val: this.state.val + 1 });
		this.setState({ val: this.state.val + 1 });
		this.setState({ val: this.state.val + 1 });
	}
	render() {
		return (
			<div onClick={this.batchUpdates}>
				{`Counter is: ${this.state.val}`}//1
			</div>
		)
	}
}

setState情况总结:

  1. setState只在合成事件和生命周期中是“异步”的,在原生事件和setTimeout中都是“同步”的
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和生命周期钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,也可以通过设置setState(partialState, callback)中的第二个参数callback拿到更新后的结果。
  3. setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新。在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行。如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

生命周期

如下图,看图说明生命周期时分为挂载时、更新时、卸载时来分别说明。
在这里插入图片描述

render
  1. 是class组件必需的方法。
  2. 获取最新的props和state
  3. 不修改组件state的情况下,每次调用时都返回相同的结果
constructor

如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数

  1. 通过给this.state赋值对象来初始化内部state。
  2. 为事件处理函数绑定实例。
constructor(props) {
	super(props);
	this.state = {counter: 0};
	this.handleClick = this.handleClick.bind(this);
}
//1.不要在此调用setState();2.避免将props的值赋值给state。
componentDidMount
  1. 会在组件挂载后(插入DOM树中)立即调用
  2. 依赖于DOM节点的初始化应该放在这里,如需通过网络请求获取的数据
  3. 可以在此生命周期里加setState,但发生在浏览器更新屏幕之前,会导致性能问题;
  4. 在render阶段的constructor中初始化state,但有更新时可在此生命周期中setState。
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot);

会在更新后被立即调用,首次渲染不会执行此方法

componentDidUpdate(prevProps) {
	//典型⽤法(不要忘记⽐较 props):加条件判断,不然死循环
	if(this.props.userID !== prevProps.userID) {
		this.fetchData(this.props.userID);
	}
}

如果组件实现了 getSnapshotBeforeUpdate() ⽣命周期, 则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
如果 shouldComponentUpdate() 返回值为 false,则不会调⽤ componentDidUpdate()。

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。例如,清除 timer,取消网络请求; componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。

shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)

不常用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发⽣变化组件都会重新渲染。 作为性能优化使⽤,返回false可以跳过re-render。
shouldComponentUpdate() 返回 false,不会调用 UNSAFE_componentWillUpdate()、render() 和 componentDidUpdate()。

getDerivedStateFromProps

不常用。是为了取代componentWillReceiveProps和componentWillUpdate设置的。根据props的变化改变state,它应该返回一个对象来更新state,如果返回null则不更新任何内容。

  1. 在使用此生命周期时,要注意把传入的prop值和之前传入的prop进行比较
  2. 因为这个生命周期是静态方法,同时要保持它是纯函数,不要产生副作用。
static  getDerivedStateFromProps(nextProps, prevState) {
	const {type} = nextProps;
	//当传入的type发生变化时,更新state
	if(type !== prevState.type) {
		return { type };
	}
	//否则对于state不进行任何操作
	return null;
}

class ColorPicker extends React.Component {
	state = {
		color: '#000'
	};
	static getDerivedStateFromProps(props, state) {
		if(props.color !== state.color) {
			return { color: props.color }
		}
		return null;
	}
	//选择颜色方法
	render() {
		//显示颜色和选择颜色操作
		setState({ color: XXX })
	}
}

class ColorPicker extends React.Component {
	state = {
		color: '#000',
		prevPropColor: ''//setState和forceUpdate也会触发此生命周期,会覆盖
	}
	static getDerivedStateFromProps(props, state) {
		if(props.color !== state.prevPropColor) {
			if(props.color !== state.prevPropColor) {
				return {
					color: props.color,
					prevPropColor: props.color
				}
			}
			return null;
		}
		//选择颜色方法
		render() {
			//显示颜色和选择颜色操作
		}
	}
}
getSnapshotBeforeUpdate

不常用。

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate()在最近一次渲染输出(提交到DOM节点)之前调用;此生命周期方法的任何返回值将作为参数传递给componentDidUpdate()。

class ScrollingList extends React.Component {
	constructor(props) {
		super(props);
		this.listRef = React.createRef();
	}
	getSnapshotBeforeUpdate(prevProps, prevState) {
		//是否在list中添加新的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>
		)
	}
}
static getDeriveStateFromError

不常用。配合Error boundAries使用,此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新state。

componentDidCatch

不常用。会在提交阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况
语法:

componentDidCatch(error, info);

案例:

class ErrorBoundary extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			hasError: false;
		}
	}
	static getDerivedStateFromError(error) {
		//更新state
		return {
			hasError: true
		}
	}
	componentDidCatch(error, info) {
		logComponentStackToMyService(info.componentStack);
	}
	render() {
		if(this.state.hasError) {
			//可以渲染任何的自定义降级UI
			return <h1>Something went wrong.</h1>;
		}
		return this.props.children;
	}
}
UNSAFE_componentWillMount

不建议使用。UNSAFE_componentWillMount() 在挂载之前被调用;它在 render() 之前调⽤,因此在此方法中同步调⽤ setState() 不会⽣效;需要的话用componentDidMount替代。

UNSAFE_componentWillReceiveProps

不建议使用。UNSAFE_componentWillReceiveProps() 会在已挂载的组件接收新的 props 之前被调⽤; 如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.props 和 nextProps 并在此 方法中使用 this.setState() 执行 state 转换。

UNSAFE_componentWillUpdate

不建议使用。

  1. 当组件收到新的 props 或 state 时,会在渲染之前调用 UNSAFE_componentWillUpdate();
  2. 使用此作为在更新发⽣之前执⾏准备更新的机会;
  3. 初始渲染不会调⽤此方法; 如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate()。

事件处理

语法格式
  1. 在JSX元素上添加事件,通过on*EventType这种内联方式添加,命名采用小驼峰式(camelCase)的形式而不是纯小写。(原生HTML中对DOM元素绑定事件,事件类型是小写的)
  2. 无需调用addEventListener进行事件监听,也无需考虑兼容性,React已经封装好了一些的事件类型属性。
  3. 使用JSX语法时需要传入一个函数作为事件处理函数,而不是一个字符串。
  4. 不能通过返回false的方式阻止默认行为。必须显式地使用preventDefault。
<!--DOM-->
<button onclick='activateLasers()'>
	Activate Lasers
</button>
<!--React-->
<button onClick={activateLasers}>
	Activate Lasers
</button>
//js
<form onsubmit="console.log('you clicked sumbit.'); return false">
	<button type='submit'>submit</button>
</form>
//react:一般不需要使用addEventListener为已创建的DOM元素添加监听器
function Form() {
	function handleSubmit(e) {
		e.preventDefault();
		console.log('you clicked sumbit.');
	}
	return (
		<form onSubmit={handleSubmit}>
			<button type='submit'>submit</button>
		</form>
	)
}
绑定this
class Toggle extends React.Component {
	constructor(prop) {
		super(props);
		this.state = {
			isToggleOn: true;
		}
		//1.在构造函数中绑定
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick() {
		this.setState(prevState => ({
			isToggleOn: !prevState.isToggleOn
		}));
	}
	//2.直接使用箭头函数的写法
	handleClick = () => {
		this.setState(prevState => ({
			isToggleOn: !prevState.isToggleOn
		}));
	}
	render() {
		return (
			//this的值为undefined
			<button onClick={this.handleClick}>
				{this.state.isToggleOn ? 'on':'off'}
			</button>
			//3.直接在调用事件时使用bind绑定this
			<button onClick={this.handleClick().bind(this)}>
				{this.state.isToggleOn ? 'on':'off'}
			</button>
			//4.使用箭头函数在指定事件时绑定:但每次render都会创建不同的回调函数,如果该回调函数作为props传入子组件,每次子组件都要重新render
			<button onClick={() => this.handleClick()}>
				{this.state.isToggleOn ? 'on':'off'}
			</button>
		)
	}
}
ReactDOM.render(
	<Toggle/>,
	document.getElementById('root')
);

接收参数
  1. 通过对象e会被作为第二个参数传递;
  2. 通过箭头函数的方式,事件对象必须显式进行传递;
  3. 通过Function.prototype.bind的方式,事件对象以及更多的参数将会被隐式的进行传递。
<button onClick={e => this.deleteRow(id, e)}>delete row</button>
<button onClick={this.deleteRow.bind(this, id)}>delete row</button>

条件渲染

if else 渲染
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {
      isLoggedIn: false
    }
  }
  handleLoginClick() {
    this.setState({
      isLoggedIn: true
    })
  }
  handleLogoutClick() {
    this.setState({
      isLoggedIn: false
    })
  }
  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}/>
      </div>
    )
  }
}
ReactDOM.render(
	<LoginControl/>,
	document.getElementById('root')
)
与运算符&&
function Mailbox(props) {
	const unreadMessages = props.unreadMessage;
	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的表达式,会跳过元素,但会返回该表达式
render() {
  const count = 0;
  return (
    <div>
     { count && <h1>Messages: {count}</h1>}
    </div>
 );
}
三元运算符
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
     {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
       : <LoginButton onClick={this.handleLoginClick} />
     }
    </div>
 );
}
如何阻止组件渲染
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')
);

“&&”它跟 “?.” 的区别:
为假时前面返回false,后面返回undefined。

列表

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number => {
    <li key={number.toString()}>
      {number}
    </li>
  })
  return (
    <ul>{listItems}</ul>
  )
}
const numbers = [1,2,3,4,5];
ReactDOM.render(
  <NumberList numbers={numbers}/>,
  document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不⽤index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使⽤索引⽤作为列表项⽬的 key 值;
key注意点

key要保留在map的遍历元素上。

// demo1
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')
);
// demo2
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')
);
// demo3
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
     {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
     )}
    </ul>
 );
}

create-react-app

是一个官方支持的创建React单页应用程序的脚手架。它提供了一个零配置的现代化配置设置。
create-react-app创建React应用发生的流程:

  1. 执行create-react-app projectName命令。
  2. 先判断node版本是否小于10:
    1. 是:退出进程。
    2. 否:继续。
  3. init()
  4. 是否传入项目名projectName:
    1. 否:退出进程。
    2. 是:继续。
  5. 当前CRA版本是否小于latest版本:
    1. 是:退出进程。
    2. 否:继续。
  6. 检查projectName是否符合规范:
    1. 否:退出进程。
    2. 是:继续。
  7. name: appName, version: '0.1.0'写入package.json。
  8. 判断使用yarn还是npm:处理yarn/npm的相关逻辑。
  9. run()
  10. 选择模板:默认是cra-template。若发生错误,进入catch处理(删除相关的垃圾文件),退出进程。
  11. 确认需要安装的依赖:安装react和template相关依赖。若发生错误,进入catch处理(删除相关的垃圾文件),退出进程。
  12. install():若发生错误,进入catch处理(删除相关的垃圾文件),退出进程。
  13. executeNodeScript():通过require('react-scripts/scripts/init.js引入文件,进入react-app-scripts/init.js文件的处理阶段。
  14. init()
  15. 修改package.json:按照一些规则写入template的package.json。
  16. 拷贝模板文件
  17. 初始化git仓库
  18. 安装项目依赖(其中会判断是否需要TS)
  19. 删除模板文件
  20. Success

immutable及immer

immutable

解决的问题:
JavaScript中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单引用了原始对象,改变新的对象将影响到原始对象。
foo={a:1}; bar=foo; bar.a=2。此时foo.a也变成了2。
虽然这样做可以节约内存,但当应用复杂后,就造成了非常大的隐患,Mutable带来的优点变得得不偿失。
为了解决这个问题,一般的做法是使用shallowCopy(浅拷贝)或deepCopy(深拷贝)来避免被修改,但这样做造成了CPU和内存的浪费

什么是immutable data?

  1. immutable data就是一旦创建,就不能再被更改的数据
  2. 对immutable对象的任何修改或添加操作都会返回一个新的immutable对象
  3. immutable实现的原理是Persistent Data Structure(持久化数据结构):即使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免deepCopy把所有节点都复制一遍带来的性能损耗,immutable使用了Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享。

immutable.js:
与 React 同期出现,但没有被默认放到 React ⼯具集里(React 提供了简化的 Helper)。
它内部实现了⼀套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record、Seq。有非常全⾯的map、filter、 groupBy、reduce``find函数式操作⽅法。同时 API 也尽量与 Object 或 Array 类似。

//原本写法
let foo = { a: {b:1} };
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b); //2
console.log(foo === bar); //true

//使用immutable.js
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b:1}});
bar = foo.setIn(['a', 'b'], 2); //使用setIn赋值
console.log(foo.getIn(['a', 'b'], 2);//使用getIn取值
console.log(foo === bar); //false

immutable.js的优点:

  1. 降低了mutable带来的复杂性:
function touchAndLog(touchFn) {
	let data = { key: 'value' };
	touchFn(data);
	console.log(data.key);
	//因为不知道touchFn进行了什么操作,所以无法预料,但使用immutable肯定是value
}
  1. 节省内存:会尽量复用内存,甚至以前使用的对象也可以再次被复用。没有被引用的对象会被垃圾回收。
import {Map} from 'immutable';
let a = Map({
	select: 'users',
	filter: Map({ name: 'cam' })
});
let b = a.set('select', 'people');
a === b; //false
a.get('filter') === b.get('filter'); //true
  1. Undo/Redo,Copy/Paste:因为每次数据都是不一样的,所有数据都可以存储在数组里,想回退到哪里就可以拿出对应数据。

immutable的缺点:

  1. 需要学习新的API
  2. 容易与原生对象混淆:虽然immutable.js尽量尝试把API设计的原生对象类似,有的时候还是很难区别到底是immutable对象还是原生对象,容易混淆操作。
    1. immutable中的Map和List虽对应原生Object和Array,但操作非常不同,如要用map.get(‘key’)而不是map.key,array.get(0)而不是array[0]。另外immutable每次修改都会返回新对象,也很容易忘记赋值。
    2. 当使用外部库时,一般需要使用原生对象,也很容易忘记转换。

immutable.is:

//两个immutable对象可以使用===来比较,这样是直接比较内存地址,性能最好
let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2;

//为了直接比较对象的值,immutable.js提供了Immutable.is来做值比较
Immutable.is(map1, map2); //true
//Immutable.is比较的是两个对象hashCode或valueOf(对于JavaScript对象)。
//由于immutable内部使用了Trie数据结构来存储,只要两个对象hashCode相等,值就是一样的。
//这样的算法避免了深度遍历比较,性能非常好。

cursor:由于Immutable数据一般嵌套非常深,为了便于访问深层数据,Cursor提供了可以直接访问这个深层数据的引用。

import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';
let data = Immutable.fromJS({a: {b: {c:1}}});
//让cursor指向{c:1}
let cursor = Cursor.from(data, ['a', 'b'], newData => {
	//当cursor或其子cursor执行update时调用
	console.log(newData);
});
cursor.get('c'); //1
cursor = cursor.update('c', x=>x+1);
cursor.get('c'); //2

使用immutable.js优化react:

  1. React可以使用shouldComponentUpdate()进行性能优化,但它默认返回true,即始终会执行render()方法,然后做Virtual DOM比较,并得出是否需要做真实DOM更新。
  2. 可以在shouldComponentUpdate周期里执行deepCopy和deepCompare避免无意义的render,但deepFn也很耗时。
import { is } from 'immutable';
shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
	const thisProps = this.props || {}, this.thisState = this.state || {};
	if(Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) {
		return true;
	}
	for(const key in nextProps) {
		if(!is(thisProps[key], nextProps[key])) {
			return true;
		}
	}
	for(const key in nextState) {
		if(thisState[key] !== nextState[key] && !is(thisState[key], nextState[key])) {
			return true;
		}
	}
	return false;
}
immer
let currentState = {
	p: {x:[2]}
}

//Q1
let o1 = currentState;
o1.p = 1;
o1.p.x = 1;

//Q2
fn(currentState);
function fn(o) {
	o.p1 = 1;
	return o;
}

//Q3
let o3 = {
	...currentState
};
o3.p.x = 1;

//Q4
let o4 = currentState;
o4.p.x.push(1);

上述各种做法均会被修改。
如何解决引用类型对象被修改:

  1. 深度拷贝:成本较高,影响性能。
  2. ImmutableJS:非常棒的一个不可变数据结构的库,可解决上述问题,但跟Immer比起来,ImmutableJS有两个较大的不足:
    1. 需要使用者学习它的数据结构操作方式,没有Immer提供的使用原生对象的操作简单、易用;
    2. 它的操作结果需要通过toJS方法才能得到原生对象,这使得在操作一个对象时,时刻要注意操作的是原生对象还是Immutable的返回结果,稍不注意就会产生问题。
//使用immer解决上述问题
//Q1 Q3
import produce from 'immer';
let o1 = produce(currentState, draft => {
	draft.p.x = 1;
});

//Q2
import produce from 'immer';
fn(currentState);
function fn(o) {
	return produce(o, draft => {
		draft.p1 = 1;
	})
}

//Q4
import produce from 'immer';
let o4 = produce(currentState, draft => {
	draft.p.x.push(1);
});
  1. currentState:被操作对象的最初状态。
  2. draftState:根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的 任何修改都将被记录并⽤于⽣成 nextState 。在此过程中,currentState 将不受影响。
  3. nextState:根据 draftState 生成的最终状态。
  4. produce:用来生成 nextState 或 producer 的函数。
  5. producer:通过 produce 生成,用来生产 nextState。
  6. recipe:用来操作 draftState 的函数。

produce的使用:

  1. produce(currentState, recipe: (draftState) => void | draftState, ?PatchListener): nextState:
    1. 对 draftState 的修改都会反应到 nextState;
    2. Immer 使⽤的结构是共享的,nextState 在结构上⼜与 currentState 共享未修改的部分; immer⽀持⾃动冻结:通过produce⽣产的nextState是被Object.freeze的。
// Q1
let nextState = produce(currentState, (draft) => {
})
currentState === nextState; // true

// Q2
let currentState = {
  a: [],
  p: {
    x: 1
 }
}
let nextState = produce(currentState, (draft) => {
  draft.a.push(2);
})
currentState.a === nextState.a; // false
currentState.p === nextState.p; // true

const currentState = {
  p: {
    x: [2],
 },
};
const nextState = produce(currentState, draftState => {
    draftState.p.x.push(3);
});
console.log(nextState.p.x); // [2, 3]
nextState.p.x = 4;
console.log(nextState.p.x); // [2, 3]
nextState.p.x.push(5); // 报错
  1. produce(recipe: (draftState) => void | draftState, ?PatchListener)(currentState): nextState 利用高阶函数的特点,提前⽣成⼀个 producer。
let producer = produce((draft) => {
  draft.x = 2
});
let nextState = producer(currentState);

使用immerse优化react:

// 定义state
state = {
  members: [
   {
      name: 'ronffy',
      age: 30
   }
 ]
}
// 如何给member中第⼀个元素的age+1
// error
this.state.members[0].age++;
// setState
const { members } = this.state;
this.setState({
  members: [
   {
      ...members[0],
      age: members[0].age + 1,
   },
    ...members.slice(1),
 ]
})
// 使⽤reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'ADD_AGE':
      const { members } = state;
      return {
        ...state,
        members: [
         {
            ...members[0],
            age: members[0].age + 1,
         },
          ...members.slice(1),
       ]
     }
    default:
      return state
 }
}
// 使⽤immer
this.setState(produce(draft => {
  draft.members[0].age++;
}))
// 使⽤immer结合reduce
// 注意: produce 内的 recipe 回调函数的第2个参数与obj对象是指向同⼀块内存
let obj = {};
let producer = produce((draft, arg) => {
  obj === arg; // true
});
let nextState = producer(currentState, obj);
const reducer = (state, action) => produce(state, draft => {
  switch (action.type) {
    case 'ADD_AGE':
      draft.members[0].age++;
 }
})

一些面试题

setState异步顺序
  1. setState只在合成事件和生命周期中是异步的,在原生事件和setTimeout中都是同步的。
  2. setState的异步:并不是说内部由异步代码实现,而是本身执行过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新1之前,导致合成事件和钩子函数中没法立马拿到更新后的值,形成了异步。
  3. 可以通过setState(paritialState, callback)中的第二个参数callback拿到更新后的结果。
  4. setState的批量更新优化也是建立在异步(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并。
使用哪些生命周期可以完成性能优化

shouldComponentUpdate:判断是否每次state变化都要更新。

如何不使用ES6实现一个React组件?

使用create-react-class代替:

class Greeting extends React.Component {
	render() {
		return <h1>hello, {this.props.name}</h1>
	}
}
//create-react-class
var createReactClass = require('create-react-class');
var Greeting = createReactClass({
	render: function() {
		return <h1>hello, {this.props.name}</h1>;
	}
});

//1.默认属性声明
class Greeting extends React.Component {}
Greeting.defaultProps = {
	name: 'Mary'
};
var Greeting = createReactClass({
	getDefaultProps: function() {
		return {
			name: 'mary'
		};
	},
});

//2.初始化state
class SayHello extends React.Component {
	constructor(props) {
		super(props);
		this.sttae = {
			message: 'hello'
		};
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick() {
		alert(this.state.message);
	}
	render() {
		return (
			<button onClick={this.handleClick}>
				sayHello
			</button>
		)
	}
}
//createReactClass创建的实例里,组件的方法都会自动绑定上
var SayHello = createReactClass({
	getInitialState: function() {
		return {
			message: 'hello'
		};
	},
	handleClick: function() {
		alert(this.state.message);
	},
	render: function() {
		return (
			<button onClick={this.hanelClick}>sayHello</button>
		)
	}
})

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值