1、jsx = js+xml 是js的拓展语法
从本质上讲,JSX 只是为 React.createElement(component, props, ...children)
函数提供的语法糖
优点:1)执行的效率更快
2)他是类型安全的,编译的过程中就行及时的发现错误
3)在使用jsx的时候编写模板或更加简单和快速
注意:jsx中HTML标签必须按照W3C的规范写 标签必须闭合
//不能使用一个普通的表达式作为 React 元素类型。如果你想使用普通表达式来表示元素类型,首先你需要将其赋值给大写的变量。这通常会出现在根据不同的 props 渲染不同的组件时: //为了解决这个问题,首先需要将表达式赋值给一个以大写字母开头的变量。 import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = { photo: PhotoStory, video: VideoStory }; function Story(props) { // 错误!JSX 类型不能是表达式!!!!! return <components[props.storyType] story={props.story} />; // 正确!JSX 类型可以是一个以大写字母开头的变量. const SpecificStory = components[props.storyType]; return <SpecificStory story={props.story} />;
//在 JavaScript 中,if 语句和 for 循环不是表达式,因此不能在 JSX 中直接使用。但你可以将他们放在附近的代码块中,例如: function NumberDescriber(props) { let description; if (props.number % 2 == 0) { description = <strong>even</strong>; } else { description = <i>odd</i>; } return <div>{props.number} is an {description} number</div>; }
属性展开
//如果你已经有一个 object 类型的 props,并且希望在 JSX 中传入,你可以使用扩展操作符 ... 传入整个 props 对象。这两个组件是等效的: function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; } //你也可以挑选你的组件将使用的指定属性(props),同时使用展开运算符传递所有其他属性(props)。 const Button = props => { const { kind, ...other } = props; const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton"; return <button className={className} {...other} />; }; const App = () => { return ( <div> <Button kind="primary" onClick={() => console.log("clicked!")}> Hello World! </Button> </div> ); }; //在上面的例子中,kind 属性(props)被安全地使用,并且 不被 传递给 DOM 中的 <button> 元素。 所有其他属性(props)都通过 ... other 对象传递,使得这个组件非常灵活。 你可以看到它传递了onClick 和 children 属性(props)。 //展开(Spread) 属性可能很有用,但它们还可以轻松地将不必要的属性(props)传递给不关心它们的组件,或将无效的HTML属性传递给DOM。我们建议谨慎使用此语法!!!!!!!!
React组件也可以返回一个元素数组
render() { // No need to wrap list items in an extra element! return [ // Don't forget the keys :) <li key="A">First item</li>, <li key="B">Second item</li>, <li key="C">Third item</li>, ]; }
Functions(函数) 作为 Children(子元素)
//通常情况下,嵌入到 JSX 中的 JavaScript 表达式会被认为是一个字符串、React元素 或者是这些内容的一个列表。然而, props.children 类似于其他的 props(属性) ,可以被传入任何数据,而不是仅仅只是 React 可以渲染的数据。例如,如果有自定义组件,其 props.children 的值可以是回调函数 function Repeat(props){ let items = []; for(let i = 0;i<props.numTimes;i++){ items.push(props.children(i)) } return <div>{items}</div> } funxtion ListOfTenThings(){ return ( <Repeat numTimes={10}> {(index) => <div>this is item {index} in the list</div>} </Repeat> ) }
Booleans, Null, 和 Undefined 被忽略
//false,null,undefined,和 true 都是有效的的 children(子元素) 。但是并不会被渲染,下面的JSX表达式渲染效果是相同的 <div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div> //在有条件性渲染 React 元素时非常有用。如果 showHeader 为 true 时,<Header />会被渲染: <div> {showHeader && <Header />} <Content /> </div> //需要注意的是“falsy”值,例如数值 0 ,仍然会被 React 渲染。例如,这段代码不会按照你预期的发生,因为当 props.messages 是一个空数组时 0 会被打印 <div> {props.messages.length && <MessageList messages={props.messages} /> } </div> //要修复这个问题,确保 && 之前的表达式总是布尔值: <div> {props.messages.length > 0 && <MessageList messages={props.messages} /> } </div> //反过来,如果在输出中想要渲染 false ,true,null 或者 undefined ,你必须先将其转化为字符串: <div> My JavaScript variable is {String(myVariable)}. </div>
2、react 开发环境的搭建
1)react.js 核心文件 (npm i react --save)
2)react-dom.js 渲染页面中的DOM 当前文件依赖于react核心文件 (npm i react-dom --save)
3)babel.js ES6转换成ES5 jsx语法转换成js 现今浏览器进行代码的兼容 (npm i babel-standalone --save)
3、注释/多行标签需要有一个父元素包裹
let MyDOm = (<div> {/*此处是注释*/} <div>上面是注释</div> </div>) ReactDOM.render(MyDom,document.getElementId("root"))
4、hook
export default () => { const [count,setCount] = useState(0) //const [count,setCount] = useState(() = > {return 0}) 同上 return (<div> {count} <button onClick={() = > setConut(x => x+1)}> +1 </button> </div>) }
5、可以用 花括号 把任意的 JavaScript 表达式 嵌入到 JSX 中。例如,2 + 2
6、如果是空标签,您应该像 XML 一样,使用 />
立即闭合它 :
const element = <img src={user.avatarUrl} />;
7、Babel 将JSX编译成 React.createElement()
调用
//下面的两个例子是是完全相同的: const element = ( <h1 className="greeting"> Hello, world! </h1> ); const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); //React.createElement() 会执行一些检查来帮助你编写没有bug的代码,但基本上它会创建一个如下所示的对象: // 注意: 这是简化的结构 const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
8、函数式组件和累组件
//函数式组件 function Welcome (props){ return <h1>Hello,{props.name}</h1> } //类组件 class Welcome extends React.Component { render(){ return <h1>Hello,{this.props.name}</h1> } }
9、纯函数和非纯函数 :React 组件都必须是纯函数,并禁止修改其自身 props
//纯函数 function sum(a, b) { return a + b; } //非纯函数:它改变了自身的输入值 function withdraw(account, amount) { account.total -= amount; }
10、state(状态)更新可能是异步的
React 为了优化性能,有可能会将多个 setState()
调用合并为一次更新。
因为 this.props
和 this.state
可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
//例如, 以下代码可能导致 counter(计数器)更新失败: //错误 this.setState({ counter:this.state.counter + this.props.increment, }) //要解决这个问题,应该使用第 2 种 setState() 的格式,它接收一个函数,而不是一个对象。该函数接收前一个状态值作为第 1 个参数, 并将更新后的值作为第 2 个参数: //正确 this.setState((state,props)=>({ counter:state.counter + props.increment }))
11、定时器在componentMount中设置,需要在componentWillUNmount中销毁
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); }
12、正确使用state
1)不要直接修改state
// 错误 this.state.comment = 'Hello'; // 正确 this.setState({comment: 'Hello'});
2)state跟新可能是异步的
React 为了优化性能,有可能会将多个 setState()
调用合并为一次更新。
因为 this.props
和 this.state
可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
例如, 以下代码可能导致 counter
(计数器)更新失败:
// 错误 this.setState({ counter: this.state.counter + this.props.increment, }); //要解决这个问题,应该使用第 2 种 setState() 的格式,它接收一个函数,而不是一个对象。该函数接收前一个状态值作为第 1 个参数, 并将更新后的值作为第 2 个参数: // 正确 this.setState((state, props) => ({ counter: state.counter + props.increment })); //我们在上面使用了一个箭头函数,但是也可以使用一个常规的函数: // 正确 this.setState(function(state, props) { return { counter: state.counter + props.increment }; });
3)state跟新会被合并
当你调用 setState()
, React 将合并你提供的对象到当前的状态中。
合并是浅合并,所以 this.setState({comments})
不会改变 this.state.posts
的值,但会完全替换this.state.comments
的值。
13、处理事件
1)react 事件使用驼峰命名,而不是全部小写。
2)通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串。
//例如,HTML: <button onclick="activateLasers()"> Activate Lasers </button> //在 React 中略有不同: <button onClick={activateLasers}> Activate Lasers </butto//实事件处理:react绑定事件用小驼峰,在绑定函数的时候不能加(),不然会立即执行
//1、修改this的指向 //2、bind方法原地绑定 //3、函数通过箭头函数进行创建 //4、constructor中提前绑定 //5、把事件调用写成箭头函数的形式 class Com extends React.COmponent{ constructor(props){ super(props) this.funcC = this.funcC.bind(this) } funcA(){ console.log(this) } funcB = ()=>{ console.log(this) } funcC(){ console.log(this) } funcD(){ console.log(this) } funcE = (this,i) =>{ console.log(i) console.log(this) } funcF = (i,e) =>{ console.log(i) console.log(e) } render(){ return( <div> //我是组件 <button onClick = {this.funcA.bind(this)}>bind方式绑定</button> <button onClick = {this.funcB}>bind方式绑定</button> <button onClick = {this.funcC}>bind方式绑定</button> <button onClick = {() => {this.funcD()}}>bind方式绑定</button> <button onClick = {this.funcE.bind(this,'我是参数1','我是参数2')}>参数1</button> <button onClick = {this.funcF('我是参数1','我是参数2',e)}>参数2</button> </div> ) } }
3)另一个区别是,在 React 中你不能通过返回 false
(愚人码头注:即 return false;
语句) 来阻止默认行为。必须明确调用 preventDefault
。
//例如,对于纯 HTML ,要阻止链接打开一个新页面的默认行为,可以这样写: <a href="#" onclick="console.log('The link was clicked.'); return false"> Click me </a> //在 React 中, 应该这么写: function ActionLink() { function handleClick(e) { //这里, e 是一个合成的事件。 e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
4)this指向问题
//在JSX回调中你必须注意 this 的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick 并将其传递给onClick,那么在直接调用该函数时,this 会是 undefined 。 //这不是 React 特有的行为;这是 JavaScript 中的函数如何工作的一部分。 一般情况下,如果你引用一个后面没跟 () 的方法,例如 onClick={this.handleClick} ,那你就应该 绑定(bind) 该方法。 //方法1:bind方法 class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // 这个绑定是必要的,使`this`在回调中起作用 this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); } } //方法2:实验性的 属性初始化语法 ,使用属性初始值设置来正确地 绑定(bind) 回调: class LoggingButton extends React.Component { // 这个语法确保 `this` 绑定在 handleClick 中。 // 警告:这是 *实验性的* 语法。 handleClick = () => { console.log('this is:', this); } render() { return ( <button onClick={this.handleClick}> Click me </button> ); } } //方法3:回调中使用一个 箭头函数: //这个语法的问题是,每次 LoggingButton 渲染时都创建一个不同的回调。在多数情况下,没什么问题。然而,如果这个回调被作为 prop(属性) 传递给下级组件,这些组件可能需要额外的重复渲染。我们通常建议在构造函数中进行绑定,以避免这类性能问题。 class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // This syntax ensures `this` is bound within handleClick return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ); } }
4)将参数传递给事件处理程序
//在循环内部,通常需要将一个额外的参数传递给事件处理程序。 例如,如果 id 是一个内联 ID,则以下任一方式都可以正常工作: <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button> //上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
14、条件渲染
1)声明一个变量并使用一个 if
语句是一个有条件地渲染组件
render() { const isLoggedIn = this.state.isLoggedIn; let button; if (isLoggedIn) { //if语法 button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); }
2)使用逻辑&&操作符的内联if用法
//您可以 在JSX中嵌入任何表达式 ,方法是将其包裹在花括号中。这也包括 JavaScript 逻辑 && 运算符。 它有助于有条件地包含一个元素: function Mailbox(props) { const unreadMessages = props.unreadMessages; 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') );
3)另一个用于条件渲染元素的内联方法是使用 JavaScript 的条件操作符 condition ? true : false
。
4)防止组件渲染
在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null
而不是其渲染输出。
//从组件的 render 方法返回 null 不会影响组件生命周期方法的触发。 例如, componentWillUpdate 和 componentDidUpdate 仍将被调用。 function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning"> Warning! </div> ); }
15、列表(list)和键(keys)
//在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。我们可以在操作两个不同数组的时候使用相同的 keys //键是React的一个内部映射,但其不会传递给组件的内部。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。 //Post 组件可以读取 props.id,但是不能读取 props.key 。 function Post(props) { const posts = props.posts; return ( <ul> {numbers.map((post) => <ListItem key={post.id} id={post.id} title={post.title} /> )} </ul> ); }
16、表单
1)受控组件
概念:在 HTML 中,表单元素如 <input>
,<textarea>
和 <select>
表单元素通常保持自己的状态,并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState()
更新。
我们可以通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。
受控组件的替代方案:
有时使用受控组件有些乏味,因为你需要为每一个可更改的数据提供事件处理器,并通过 React 组件管理所有输入状态。当你将已经存在的代码转换为 React 时,或将 React 应用程序与非 React 库集成时,这可能变得特别烦人。在这些情况下,您可能需要使用不受控的组件,用于实现输入表单的替代技术
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); //this.setState({value: event.target.value.toUpperCase()}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
2)textarea
<textarea>
的赋值使用 value
属性替代。这样一来,表单中 <textarea>
的书写方式接近于单行文本输入框
3)select标签
//React 中,并不使用这个 selected 属性,而是在根 select 标签中使用了一个 value 属性。这使得受控组件使用更方便,因为你只需要更新一处即可。例如: class FlavorForm extends React.Component { constructor(props) { super(props) this.state = { value: 'coconut' } this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({ value: event.target.value }) } handleSubmit(event) { alert('Your favorite flavor is :' + this.state.value) event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Pick your favorite flavor: <select value={this.state.value} onChange={this.handleChange}> //<select multiple={true} value={['B', 'C']}> 您可以将一个数组传递给value
属性,允许你在select
标签中选择多个选项 <option value="grapefruit">Grapefruit</option> <option value="lime">lime</option> <option value="coconut">coconut</option> <option value="mango">mango</option> </select> </label> <input type="submit" value="Submit" /> </form> ) } }
4)file input 标签 (不受控 组件)
在HTML中, <input type="file">
可以让用户从设备存储器中选择一个或多个文件上传到服务器,或者通过 JavaScript 使用 File API 操作。
<input type="file" />
5)处理多个输入元素
当您需要处理多个受控的 input
元素时,您可以为每个元素添加一个 name
属性,并且让处理函数根据 event.target.name
的值来选择要做什么。
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; //ES6 计算的属性名称语法来更新与给定输入名称相对应的 state(状态) 键: //下面这段代码等价于ES5代码: //var partialState = {}; //partialState[name] = value; // this.setState(partialState); this.setState({ [name]: value }); } render() { return ( <form> <label> Is going: <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> Number of guests: <input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
6)如果您正在寻找一个完整的解决方案,包括验证、跟踪访问的字段以及处理表单提交,那么 Formik 是最受欢迎的选择之一。但是,它建立在受控组件和管理状态的相同原则之上 - 所以不要忽视学习它们。
17、状态提升
在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。(官方demo: http://react.html.cn/docs/lifting-state-up.html)
18、组合(Composition)VS 继承(Inheritance)
在 Facebook ,我们在千万的组件中使用 React,我们还没有发现任何用例,值得我们建议你用继承层次结构来创建组件。
使用 props(属性) 和 组合已经足够灵活来明确、安全的定制一个组件的外观和行为。切记,组件可以接受任意的 props(属性) ,包括原始值、React 元素,或者函数。
//使用特别的 children prop 来直接传递 子元素,props.children自带props,代表子组件的内容 function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> {props.children} </FancyBorder> ); } class SignUpDialog extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.state = {login: ''}; } render() { return ( <Dialog title="Mars Exploration Program" message="How should we refer to you?"> <input value={this.state.login} onChange={this.handleChange} /> <button onClick={this.handleSignUp}> Sign Me Up! </button> </Dialog> ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } }
19、优化性能
1)ES6 对于数组支持展开语法 ,使得解决上述问题更加简单。如果你使用的是Create React App,默认支持该语法
handleClick() { this.setState(state => ({ words: [...state.words, 'marklar'], })); };
//可以以一种简单的方式重写上述代码,使得改变对象的同时不会突变对象,例如,如果有一个 colormap 的对象并且编写一个函数将 colormap.right 的值改为 'blue' function updateColorMap(colormap) { colormap.right = 'blue'; } //在不突变原来的对象的条件下实现上面的要求,我们可以使用Object.assign 方法: function updateColorMap(colormap) { return Object.assign({}, colormap, {right: 'blue'}); } //updateColorMap 现在返回一个新的对象,而不是修改原来的对象。Object.assign 属于ES6语法,需要 polyfill。 //JavaScript提案添加了对象展开符 ,能够更简单地更新对象而不突变对象。 function updateColorMap(colormap) { return {...colormap, right: 'blue'}; }
2)使用 Immutable 数据结构 https://github.com/immutable-js/immutable-js
//使用 Immutable 数据结构 const SomeRecord = Immutable.Record({ foo: null }); const x = new SomeRecord({ foo: 'bar' }); const y = x.set('foo', 'baz'); const z = x.set('foo', 'bar'); x === y; // false x === z; // true