Open pfan123 opened 5 years ago
React :元素构成组件,组件又构成应用。 React 核心思想是组件化,其中组件通过属性 (props) 和 状态 (state) 传递数据。
props 是组件对外的接口,state 是组件对内的接口。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的 props 属性进行传递,因此 props 是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口 state。根据对外接口 props 和对内接口state,组件计算出对应界面的 UI。
主要区别:
当一个组件被注入一些属性(Props )值时,属性值来源于它的父级元素,所以人们常说,属性在 React 中是单向流动的:从父级到子元素。
1.props (属性) 默认为 “true”
如果你没给 prop(属性) 传值,那么他默认为 true 。下面两个 JSX 表达式是等价的:
<MyTextBox autocomplete /> <MyTextBox autocomplete={true} />
通常情况下,我们不建议使用这种类型,因为这会与 ES6 中的对象 shorthand 混淆 。ES6 shorthand 中 { foo } 指的是 { foo: foo } 的简写,而不是 { foo: true } 。这种行为只是为了与 HTML 的行为相匹配。 (举个例子,在 HTML 中,< input type=“radio” value=“1” disabled /> 与 < input type=“radio” value=“1” disabled=“true” /> 是等价的。JSX 中的这种行为就是为了匹配 HTML 的行为。)
< input type=“radio” value=“1” disabled />
< input type=“radio” value=“1” disabled=“true” />
2.props扩展
如果你已经有一个 object 类型的 props,并且希望在 JSX 中传入,你可以使用扩展操作符 … (JSX spread 用法) 传入整个 props 对象。这两个组件是等效的:
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; }
显然下面的方法更方便:因为它将数据进行了包装,而且还简化了赋值的书写
React 的核心思想是组件化,而组件中最重要的概念是 State,State 是一个组件的UI数据模型,是组件渲染时的数据依据。
状态(state) 和 属性(props) 类似,都是一个组件所需要的一些数据集合,但是state是私有的,可以认为state是组件的“私有属性(或者是局部属性)”。
如何判断是否为 State ?
组件中用到的一个变量是不是应该作为组件 State,可以通过下面的 4 条依据进行判断:
并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。
1.用setState 修改State
直接修改 state,组件并不会重新触发 render() // 错误 this.state.comment = 'Hello';
正确的修改方式是使用setState()
// 正确 this.setState({comment: 'Hello'});
2.State 的更新是异步的
综上所述: this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个 state (状态)
例:这样 counter (计数器) 会更新失败
// 错误 this.setState({ counter: this.state.counter + this.props.increment, });
要弥补这个问题,使用 setState() 的另一种形式,它接受一个函数而不是一个对象。这个函数有两个参数: (1)第一个参数: 是当前最新状态的前一个状态(本次组件状态修改前的状态) (2)第二个参数:是当前最新的属性props
// 正确 this.setState((prevState, props) => ({ counter: prevState.counter + props.increment })); //注意:下面这样是错的 this.setState((prevState, props) => { //没将{}用()括起来,所以会解析成代码块 counter: prevState.counter + props.increment });
如果你还不懂没关系,看下面例子: 我们现在渲染出一个button,想每点击一下,counter就+3 看下面代码:
class App extends React.Component { state = { counter: 0, } handleClick = () => { const { counter } = this.state; //或者 const counter = this.state.counter; this.setState({ counter: counter + 1 }); this.setState({ counter: counter + 1 }); this.setState({ counter: counter + 1 }); } render() { return ( <div> counter is: {this.state.counter} <button onClick={this.handleClick} >点我</button> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'));
每点击一下,加 +1,并不是 +3
之所以+1,不是 +3,是因为 state 的更新可能是异步的,React 会把传入多个 setState 的多个 Object “batch” 起来合并成一个。合并成一个就相当于把传入 setState 的多个 Object 进行 shallow merge,像这样:
const update = { counter: counter + 1, counter: counter + 1, counter: counter + 1 //因为上面三句话都一样,所以会当一句话执行 }
我们可以这么做就会成功:看下面
class App extends React.Component { state = { counter: 0, } handleClick = () => { this.setState(prev => ({ counter: prev.counter + 1 })); this.setState(prev => ({ counter: prev.counter + 1 })); this.setState(prev => ({ counter: prev.counter + 1 })); //这样是错的 this.setState(prev => {counter: prev.counter + 1}); //这样是错的 this.setState(prev => {counter:++prev.counter}); //这样是错的 this.setState(prev => {counter:prev.counter++}); } render() { return ( <div> counter is: {this.state.counter} <button onClick={this.handleClick} >点我</button> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'));
之所以成功是因为:传入多个 setState 的多个 Object 会被 shallow Merge,而传入多个 setState 的多个 function 会被 "queue" 起来,queue 里的 function 接收到的 state(上面是 prev )都是前一个 function 操作过的 state。
3.State更新会被合并
官方文档看不懂不要紧,直接举个例子你就懂了。
例如一个组件的状态为:
this.state = { title : 'React', content : 'React is an wonderful JS library!' }
当只需要修改状态 title 时,只需要将修改后的 title 传给 setState:
this.setState({title: 'Reactjs'});
React 会合并新的 title 到原来的组件状态中,同时保留原有的状态 content,合并后的 State 为:
{ title : 'Reactjs', content : 'React is an wonderful JS library!' }
4.setState里顺序更新
// history 为数组 this.setState({ history: history.concat([1]), //(1) current: history.length, //(2) nextPlayer: !nextPlayer, //(3) });
执行 setState 时:先更新 history,然后再用更新改变后的 history 计算 current 的值,最后再更新 nextPlayer
当状态发生变化时,如何创建新的状态?根据状态的类型,可以分成三种情况:
1.状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)
这种情况最简单,直接给要修改的状态赋一个新值即可
// 原state this.state = { count: 0, title : 'React', success:false } // 改变state this.setState({ count: 1, title: 'bty', success: true })
2.状态的类型是数组
数组是一个引用,React 执行 diff 算法时比较的是两个引用,而不是引用的对象。所以直接修改原对象,引用值不发生改变的话,React 不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的数组或对象。 (1)增加 如有一个数组类型的状态 books,当向 books 中增加一本书 (chinese) 时,使用数组的 concat 方法或 ES6 的数组扩展语法
// 方法一:将 state 先赋值给另外的变量,然后使用 concat 创建新数组 let books = this.state.books; this.setState({ books: books.concat(['chinese']) }) // 方法二:使用preState、concat创建新数组 this.setState(preState => ({ books: preState.books.concat(['chinese']) })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'chinese'] }))
(2)截取 当从 books 中截取部分元素作为新状态时,使用数组的 slice 方法:
// 方法一:将state先赋值给另外的变量,然后使用slice创建新数组 let books = this.state.books; this.setState({ books: books.slice(1,3) }) // 方法二:使用preState、slice创建新数组 this.setState(preState => ({ books: preState.books.slice(1,3) }))
(3)条件过滤 当从 books 中过滤部分元素后,作为新状态时,使用数组的 filter 方法:
// 方法一:将state先赋值给另外的变量,然后使用filter创建新数组 var books = this.state.books; this.setState({ books: books.filter(item => { return item != 'React'; }) }) // 方法二:使用preState、filter创建新数组 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }) }))
注意:不要使用 push、pop、shift、unshift、splice 等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而 concat、slice、filter 会返回一个新的数组。
3.状态的类型是普通对象(不包含字符串、数组) 对象是一个引用,React 执行 diff 算法时比较的是两个引用,而不是引用的对象。所以直接修改原对象,引用值不发生改变的话,React 不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的对象。 使用 ES6 的 Object.assgin 方法
// 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象 var owner = this.state.owner; this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}) }) // 方法二:使用preState、Object.assign创建新对象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}) }))
使用对象扩展语法(object spread properties)
// 方法一:将state先赋值给另外的变量,然后使用对象扩展语法创建新对象 var owner = this.state.owner; this.setState({ owner: {...owner, name: 'Jason'} }) // 方法二:使用preState、对象扩展语法创建新对象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'} }))
综上所述: 创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。
我们说 props 是组件对外的接口,state 是组件对内的接口。 一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):
<MyComponent title={this.state.title}/>
这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。
如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
父组件:
class Parent extends PureComponent { constructor(props) { super(props); console.log('Parent constructor'); } getDerivedStateFromProps() { console.log('Parent shouldComponentUpdate'); } shouldComponentUpdate() { console.log('Parent shouldComponentUpdate'); } componentDidMount() { console.log('Parent componentDidMount'); } componentDidUpdate(prevProps, prevState) { console.log('Parent componentDidUpdate(prevProps, prevState)'); } componentWillUnmount() { console.log('Parent componentWillUnmount'); } render() { console.log('Parent render'); return ( <div className="root"> <h3>This is Parent</h3> <Child /> </div> ); } }
子组件:
class Child extends PureComponent { constructor(props) { super(props); console.log('Child constructor'); } getDerivedStateFromProps() { console.log('Child shouldComponentUpdate'); } shouldComponentUpdate() { console.log('Child shouldComponentUpdate'); } componentDidMount() { console.log('Child componentDidMount'); } componentDidUpdate(prevProps, prevState) { console.log('Child componentDidUpdate(prevProps, prevState)'); } componentWillUnmount() { console.log('Child componentWillUnmount'); } render() { console.log('Child render'); return ( <div className="child"> <h4>I am a Child</h4> </div> ); } }
运行后结果如下:
Parent constructor Parent getDerivedStateFromProps Parent shouldComponentUpdate Parent render Child constructor Child getDerivedStateFromProps Child shouldComponentUpdate Child render Child componentDidMount Parent componentDidMount
此时可以分析出,当父组建 render 时遇到子组件,然后进入子组件的生命周期,当执行完子组件生命周期中的componentDidMount 时会回到父组建继续执行父组建未完成的生命周期。
由上面父子嵌套组件的生命周期流程,可以推断继续验证多级组件嵌套的流程。
React :元素构成组件,组件又构成应用。 React 核心思想是组件化,其中组件通过属性 (props) 和 状态 (state) 传递数据。
State 与 Props 区别
props 是组件对外的接口,state 是组件对内的接口。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的 props 属性进行传递,因此 props 是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口 state。根据对外接口 props 和对内接口state,组件计算出对应界面的 UI。
主要区别:
Props的使用
当一个组件被注入一些属性(Props )值时,属性值来源于它的父级元素,所以人们常说,属性在 React 中是单向流动的:从父级到子元素。
1.props (属性) 默认为 “true”
如果你没给 prop(属性) 传值,那么他默认为 true 。下面两个 JSX 表达式是等价的:
通常情况下,我们不建议使用这种类型,因为这会与 ES6 中的对象 shorthand 混淆 。ES6 shorthand 中 { foo } 指的是 { foo: foo } 的简写,而不是 { foo: true } 。这种行为只是为了与 HTML 的行为相匹配。 (举个例子,在 HTML 中,
< input type=“radio” value=“1” disabled />
与< input type=“radio” value=“1” disabled=“true” />
是等价的。JSX 中的这种行为就是为了匹配 HTML 的行为。)2.props扩展
如果你已经有一个 object 类型的 props,并且希望在 JSX 中传入,你可以使用扩展操作符 … (JSX spread 用法) 传入整个 props 对象。这两个组件是等效的:
显然下面的方法更方便:因为它将数据进行了包装,而且还简化了赋值的书写
State
State 是什么
React 的核心思想是组件化,而组件中最重要的概念是 State,State 是一个组件的UI数据模型,是组件渲染时的数据依据。
状态(state) 和 属性(props) 类似,都是一个组件所需要的一些数据集合,但是state是私有的,可以认为state是组件的“私有属性(或者是局部属性)”。
如何判断是否为 State ?
组件中用到的一个变量是不是应该作为组件 State,可以通过下面的 4 条依据进行判断:
并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。
如何正确使用 State
1.用setState 修改State
正确的修改方式是使用setState()
2.State 的更新是异步的
综上所述: this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个 state (状态)
例:这样 counter (计数器) 会更新失败
要弥补这个问题,使用 setState() 的另一种形式,它接受一个函数而不是一个对象。这个函数有两个参数: (1)第一个参数: 是当前最新状态的前一个状态(本次组件状态修改前的状态) (2)第二个参数:是当前最新的属性props
如果你还不懂没关系,看下面例子: 我们现在渲染出一个button,想每点击一下,counter就+3 看下面代码:
每点击一下,加 +1,并不是 +3
之所以+1,不是 +3,是因为 state 的更新可能是异步的,React 会把传入多个 setState 的多个 Object “batch” 起来合并成一个。合并成一个就相当于把传入 setState 的多个 Object 进行 shallow merge,像这样:
我们可以这么做就会成功:看下面
之所以成功是因为:传入多个 setState 的多个 Object 会被 shallow Merge,而传入多个 setState 的多个 function 会被 "queue" 起来,queue 里的 function 接收到的 state(上面是 prev )都是前一个 function 操作过的 state。
3.State更新会被合并
官方文档看不懂不要紧,直接举个例子你就懂了。
例如一个组件的状态为:
当只需要修改状态 title 时,只需要将修改后的 title 传给 setState:
React 会合并新的 title 到原来的组件状态中,同时保留原有的状态 content,合并后的 State 为:
4.setState里顺序更新
执行 setState 时:先更新 history,然后再用更新改变后的 history 计算 current 的值,最后再更新 nextPlayer
根据 State 类型 更新
当状态发生变化时,如何创建新的状态?根据状态的类型,可以分成三种情况:
1.状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)
这种情况最简单,直接给要修改的状态赋一个新值即可
2.状态的类型是数组
数组是一个引用,React 执行 diff 算法时比较的是两个引用,而不是引用的对象。所以直接修改原对象,引用值不发生改变的话,React 不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的数组或对象。 (1)增加 如有一个数组类型的状态 books,当向 books 中增加一本书 (chinese) 时,使用数组的 concat 方法或 ES6 的数组扩展语法
(2)截取 当从 books 中截取部分元素作为新状态时,使用数组的 slice 方法:
(3)条件过滤 当从 books 中过滤部分元素后,作为新状态时,使用数组的 filter 方法:
注意:不要使用 push、pop、shift、unshift、splice 等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而 concat、slice、filter 会返回一个新的数组。
3.状态的类型是普通对象(不包含字符串、数组) 对象是一个引用,React 执行 diff 算法时比较的是两个引用,而不是引用的对象。所以直接修改原对象,引用值不发生改变的话,React 不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的对象。 使用 ES6 的 Object.assgin 方法
使用对象扩展语法(object spread properties)
综上所述: 创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。
State 向下流动
我们说 props 是组件对外的接口,state 是组件对内的接口。 一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):
这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。
如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
嵌套组件树生命周期
父组件:
子组件:
运行后结果如下:
此时可以分析出,当父组建 render 时遇到子组件,然后进入子组件的生命周期,当执行完子组件生命周期中的componentDidMount 时会回到父组建继续执行父组建未完成的生命周期。
由上面父子嵌套组件的生命周期流程,可以推断继续验证多级组件嵌套的流程。