赞
踩
React是一个声明式、高效且灵活的用于构建用户界面的JavaScript库。使用React可以将一些简短的、独立的代码片段组合成复杂的UI界面,这些代码片段称为组件。
UI = render(data) => 单向数据流
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称为JS的语法扩展,将UI与逻辑层耦合在组件⾥,⽤{}标识。因为 JSX 语法上更接近 JS ⽽不是 HTML,所以使⽤ camelCase(小驼峰命名)来定义属性的名称; JSX ⾥的 class 变成了 className,而 tabindex 则变为 tabIndex。
//变量
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>
);
const element = (
<h1 className='a'>haha</h1>
);
//等同于
const element = React.createElement('h1', { className:'a' }, 'haha');
const element = {
type: 'h1',
props: {
className: 'a',
children: 'haha'
}
}
ReactDOM.render(element, document.getElementById('root');
//render只能代表当前时刻的状态;更新元素只能再次ReactDOM.render
从概念上类似于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> ) }
所有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);
如何避免多次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'));
注意事项:
//异步更新
this.setState({
counter: this.state.counter + this.props.increment
});
//同步更新
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
异步目的:批量处理,性能优化。
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情况总结:
如下图,看图说明生命周期时分为挂载时、更新时、卸载时来分别说明。
如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。
constructor(props) {
super(props);
this.state = {counter: 0};
this.handleClick = this.handleClick.bind(this);
}
//1.不要在此调用setState();2.避免将props的值赋值给state。
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() 会在组件卸载及销毁之前直接调用。例如,清除 timer,取消网络请求; componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。
shouldComponentUpdate(nextProps, nextState)
不常用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发⽣变化组件都会重新渲染。 作为性能优化使⽤,返回false可以跳过re-render。
shouldComponentUpdate() 返回 false,不会调用 UNSAFE_componentWillUpdate()、render() 和 componentDidUpdate()。
不常用。是为了取代componentWillReceiveProps和componentWillUpdate设置的。根据props的变化改变state,它应该返回一个对象来更新state,如果返回null则不更新任何内容。
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(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> ) } }
不常用。配合Error boundAries使用,此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新state。
不常用。会在提交阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况。
语法:
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() 在挂载之前被调用;它在 render() 之前调⽤,因此在此方法中同步调⽤ setState() 不会⽣效;需要的话用componentDidMount替代。
不建议使用。UNSAFE_componentWillReceiveProps() 会在已挂载的组件接收新的 props 之前被调⽤; 如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.props 和 nextProps 并在此 方法中使用 this.setState() 执行 state 转换。
不建议使用。
<!--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> ) }
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') );
<button onClick={e => this.deleteRow(id, e)}>delete row</button>
<button onClick={this.deleteRow.bind(this, id)}>delete row</button>
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要保留在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>
);
}
是一个官方支持的创建React单页应用程序的脚手架。它提供了一个零配置的现代化配置设置。
create-react-app创建React应用发生的流程:
name: appName, version: '0.1.0'
写入package.json。require('react-scripts/scripts/init.js
引入文件,进入react-app-scripts/init.js文件的处理阶段。解决的问题:
JavaScript中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单引用了原始对象,改变新的对象将影响到原始对象。
如foo={a:1}; bar=foo; bar.a=2
。此时foo.a也变成了2。
虽然这样做可以节约内存,但当应用复杂后,就造成了非常大的隐患,Mutable带来的优点变得得不偿失。
为了解决这个问题,一般的做法是使用shallowCopy(浅拷贝)或deepCopy(深拷贝)来避免被修改,但这样做造成了CPU和内存的浪费。
什么是immutable data?
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的优点:
function touchAndLog(touchFn) {
let data = { key: 'value' };
touchFn(data);
console.log(data.key);
//因为不知道touchFn进行了什么操作,所以无法预料,但使用immutable肯定是value
}
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
immutable的缺点:
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:
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; }
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);
上述各种做法均会被修改。
如何解决引用类型对象被修改:
//使用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); });
produce的使用:
// 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); // 报错
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++; } })
shouldComponentUpdate:判断是否每次state变化都要更新。
使用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> ) } })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。