The smallest React example looks like this:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
介绍JSX
a syntax extension to JavaScript.
const element = <h1>hello, world</h1>;
这不是string也不是HTML,这是JSX。是JS语法的扩展。
用途:在UI和JS代码混合工作时,作为可视化的助手 ;另外,JSX 让React 显示很多有用的错误和警告信息。
1.Embedding Expressions in JSX .在JSX中插入JS表达式,用{}括起来。
2.JSX 本身也是Expression.。 可以把JSX用在if,for, 分配给变量,作为参数,和从函数返回。
3.可以用字符串作为属性值,可以用{}插入JS expression给属性赋值。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>
JSX更像是JavaScript,所以属性要用驼峰写法class-> className
4.如果tag是空的,可以用简写法:
const element = <img src={user.avatarUrl} />;
5.JSX tags may contain children:就是可以嵌套
6.React.createElement()方法,相等。这个对象称为 React element.
Rendering Elements
React element 是 components的组成部分。
假设有<div id="root"></div>,使用ReactDOM.render操作DOM节点:
相当于JS中的document.getElementById("root").innerHTML = element;
React element是惰性的,不能改它的属性和孩子。
因为用setInterval函数每秒调用一次tick函数,同时element内部的代码new Date(),所以React element元素更新了。
Components and Props
组件比较像JS function,接受输入( props舞台道具 )并返回React elements
最简单定义一个component:使用JS函数。(相对简洁)
这个函数是一个React component,因为它接收了一个props object argument 作为数据并返回一个React element
也可以使用ES6 class来定义一个组件:
Rendering a Component
const element = <div />;
这个React element代表Dom tag.⚠️首字母用小写,代码DOM tags.
另外,也可以element也可以代表自定义组件,⚠️首字母用大写,代表组件。
const element = <Welcome name="Sara" /> ;
当这么用时,调用Welcome组件,并传递JSX属性给这个组件,这个属性是一个object,称为object"props",最后返回组件的结果。
例子:
过程:
- We call
ReactDOM.render()
with the<Welcome name="Sara" />
element. - React calls the
Welcome
component with{name: 'Sara'}
as the props. - Our
Welcome
component returns a<h1>Hello, Sara</h1>
element as the result. - React DOM efficiently updates the DOM to match
<h1>Hello, Sara</h1>
.
组件可以在它们的输出中加入别的组件
例子:
Extracting Components
如果一个组件过于复杂,或者一个组件可以在程序中多次使用,那么可以把它提取出来。类似于Ruby中的dry原则。
Props are Read-Only
State and Lifecycle
State类似于props,但是是私有的,只能被组件控制。
当组件使用class来定义时增加了一些新的features,其中Local state就是其中之一
Converting a Function to a Class
- Create an class, with the same name, that extends React.Component.
- Add a single empty method to it called render().
- Move the body of the function into the render() method.
- Replace props with this.props in the render() body.
- Delete the remaining empty function declaration.
例子:https://codepen.io/gaearon/pen/zKRGpo?editors=0010
function tick() {
ReactDOM.render(
3步:
- Replace this.props.date with this.state.date in the render() method;
- Add a class constructor that assigns the initial this.state:
constructor使用super关键字来调用自己父类的constructor.
3. Remove the date prop from the <Clock />element. 并去掉剩余函数
结果:
Adding Lifecycle Methods to a Class
在组件类中定义特殊的方法componentDidMount(),componentWillUnmount(),当一个组件加载mounting,和卸载unmounting时。
这两个方法叫lifecycle hooks。类似于Ruby中的after_action。钩子方法。
setInterval(): 每隔指定的时间后,调用一次“函数或表达式”。直到clearInterval()被调用或者浏览器窗口关闭。1000ms =1 second; (详细解说)
() => {};箭头函数:arrow function
ES6引用,目的是让代码,更简短并且不创建自己的this。因为传统函数一旦新定义就会有自己的this,如果是一个函数嵌套一个新定义的函数,this就会弄混作用域。而箭头函数不创建自己的this,会使用上下文环境的this值。
(param..) => {statements}
(param..) => expression
//等同(param..) => {return epression; }
//Parenteses are optional when there is only one parameter name:
singleParam => {statements}
//支持参数列表解构
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6
//没有参数则需要用(), ()=> {}
//Parenthesize the body of function to return an object literal expression:
params => ({foo: bar})
fun运行时,把bind里的this的值传给它。fun本身的this的值被替换。
分析:
- <Clock />作为参数被传入ReactDOM.render()后,React调用Clock组件的constructor方法。Clock需要显示当前时间,所以它初始化了this.state,一个包含当前时间的JS对象。之后将更新这个对象,this.state。
- 然后,React调用Clock组件的render()方法。更新DOM以匹配组件的渲染输出。
- 当组件Clock的输出插入到网页对应的DOM后,React 调用 lifecycle钩子方法componentDidMount()。在这个方法内部,组件Clock要浏览器建立a timer来每秒调用一次组件的tick()方法。
- 每秒浏览器调用tick方法。在tick内部,Clock组件调用setState()方法,更新this.state对象中的当前时间。
- 由setState()方法,React可以知道this.state已经发生变化,需要再次调用render方法。因此浏览器页面上的时间发生变化。
- 如果Clock组件要从DOM移走,React调用componentWillUnmount()钩子方法。
⚠️ :在第一次调用Clock的人的人方法后,首次加载componentDidmount()方法,从英文名字Did可以看出,组件did后mount。setState和 render一前一后执行。
Handing Evenets处理事件
React events 使用驼峰命名
With JSx you pass a function as the event handler,rather than a string.
两种方法绑定上下文this:
传递参数给Event Handlers
二选一:
e参数代表了React event,作为第二个参数。
条件渲染Conditional Rendering
if 和JS相似;
反复练习:
全的案例:https://codepen.io/gaearon/pen/QKzAgB?editors=0010
代码少但是功能都有: https://codepen.io/gaearon/pen/Xjoqwm?editors=0010
handleLoginClick = () => {}
List and Keys
Basic List Component:
这组代码在严格模式下Console会弹出⚠️,
因为<li> 没有加上key关键字,类似于id。唯一识别码,用于改变和增减。
改为: <li key={number.toString()}>,⚠️key应该是唯一的,这里是唯一的。
规则:在map()里的element加上key.
规则:最好的key是唯一的识别码 ,在同等级的Siblings中。
Forms
HTMl from elements有自然的内部state,它有默认的behavior提交表格时定位到新的页面。React也支持这个行为,但是如果用JS function来处理表格的提交和存取数据会非常convenitent。 这种实现技术称为 controlled components.
Controlled Components
在HTML如<input> <textarea> <select>基于用户的输入来保存或更新它们的state。在React, 易变的state保存在组件的state property中,更新则使用setState().
案例:https://codepen.io/gaearon/pen/VmmPgp?editors=0010
疑问:
1.event.target.value是什么意思?
event是一个虚拟的事件。React定义这些synthetic events用来对应 W3C(event)标准。
个人理解:DOMEventTarget target
这是SyntheticEvent object的attributes >见扩展,每个对象有14个属性(其中4个方法)。
扩展:SyntheticEvent: https://reactjs.org/docs/events.html
我们的事件处理会被传到SyntheticEvent的实例,一个关于浏览器原生事件的跨浏览器包裹器wrapper。和原生浏览器事件的交互是一样的,包括stopPropagation()和preventDefault().
2.event.prevetnDefault();
答案:这也是SyntheticEvent实例对象的属性,一个方法,React不能使用 return false来防止默认的行为,如防止默认打开一个link到新的网页。因此必须使用preventDefault();
一个控制的组件,每个state都关联一个处理函数,因此可以修改或验证用户输入。例如,让输入的名字强制转化为大写字母。
Textarea tag
In HTML, a <textarea>
element defines its text by its children.一般是firstChild。因为在HTML DOM中,text,attribute都是节点。
而在React中,text则被当做a value attribute处理。在constructor可以初始化这个value。
Select Tag
IN HTML
而在React中,则constructor初始化这个默认选项。this.state = {value: 'coconut'};
⚠️ :select接受array传入value属性 <select multiple={true} value={['B', 'C']}>
一句话,<input type="text">. <textarea> <select>都接受a value attribute.
练习代码:https://codepen.io/gaearon/pen/JbbEzX?editors=0010
- 一类元素对应一个触发的方法。最好不要写一起,不易读。如select,和textarea的onChage={}应该分别写不同的方法。偏要写一起的话,使用name进行区分。
- preventDefault();必须得有,否则回到默认页面。
- 不要瞎尝试,看现成的案例,或找相关文档。下午耽误了不到一个小时,就是因为在select和textarea元素的属性上不清楚,竟然想自定义属性?!!
Lifting State up :
把状态放到作为祖先的组件中,子组件则可以共享这个state了。 这么做的目的:多个组件共用一个相同的变化的数据。即:lifting the shared state up to their closest common ancestor。
首先, 把TemperatureInput中的state移动到Calculator中。让它成为两个TemperatureInput实例的source of truth 。如此input会同步变化。
第一,把this.state.temperature 替换为this.props.temperature。props来自Calculator。
因为props是只读的,同时来自父类,所以TemperatureInput不能直接控制它。
解决办法是让组件可控,即让TemperatureInput从父组件接收除了温度props外还有温 度的控制方法onTemperatureChange props。
第二,在render()中,const temperature = this.props.temperature;
然后,设计Calculator组件。
state为temperature和 scale单位符号。这就是lifted up的state。它作为source of true 服务于所有子组件。
本例中,只要存储一个格式的数据即可,另一个格式随时通过换算来显示结果。
增加处理Celsius和Fahrenheit的方法,用于设置setState.
在render(){}中进行celsius和fahrenheit的数据计算。
在return()中,调用TempertureInput组件两次,分别传入state,和控制方法。
总结: recap
- React calls the function specified as onChange on the DOM <input>。本例就是在温度输入组件TemperatureInput中的handleChange() 方法.
- handleChange方法使用this.props.onTemperatureChange(),props来自父组件Calculator。
- 父组件指定了传入的props中的方法是F还是C。传入哪个方法依据我们输入input的位置。
- 在这些方法中,父组件因为调用this.setState()更新state,所以会再渲染。
- React调用父组件的render方法内部,两种格式的温度会再计算。
- 然后渲染方法会return 2个不同scale,temperature及其控制方法的TemperatureInput组件。
- React Dom更新DOM来匹配输入的值。我们刚才输入值的input框得到它当前的值,另以一个input框更新转换的温度值。
每个组件都是独立的,都可以继承一个父组件的完整state。
parseFloat(string)方法,把string-> 浮点数。
parseInt(string) 转化为整数。
Number.isNaN(),当算数运算返回一个为定义的或无法表示的值时,NaN就产生了。
React Developer Tools用于检查数据有chrome, Safari等主流版本。
codepen中使用他人的代码,先fork再选择Change View > Debug。就可以使用了。
const output = convert(input); convert是传进来的函数。
const rounded = Math.round(output *1000)/1000?
Math.round(x),返回给定数字的值四舍五入到最接近的整数。乘以1000是为了保留小数。
Composition vs Inheritance
React有强大的组成模块,因此建议程序员多用Composition而不是继承。类似Ruby min-in model 。
在Facebook中使用了上千的components,没有用到组件继承。
Props and composition可以灵活的客制化一个组件的外观和行为。 因为组件可以接收各种各样的props,包括原始的value, React element, function.
Containment:
一些组件事先不知道它们的children。因此可以使用特殊的props: {props.children }去传递children elements 然后直接进入它们的输出。
任何在<FancyBorder>JSX tag内的元素, 作为props.children被传递给FancyBorder组件。
也可以客制化传入,如下例子:
然后在SpliPane组件中插入{props.left}和{props.right}
全代码:
https://codepen.io/chentianwei411/pen/QrbXeq?editors=0010
Specialization
有时把某个组件当成特殊的某个组件。Ruby, 动物类包含有哺乳类,鸟类。
https://codepen.io/gaearon/pen/kkEaOZ?editors=0010
类似于继承。组件FancyBorder继承了Dialog中的属性title,message
composition 也可以在class定义的组件中工作得很好。
https://codepen.io/gaearon/pen/gwZbYa?editors=0010
在ES6中,${}用来代替引号拼接。类似Ruby中的插入#{}。
alert("welcome," + this.state.login );
alert(`welcome, + ${this.state.login}`) ⚠️ 是`, 单引号和双引号不行。
实例:用React设计一个小的功能:
在React中有两类model data:props and state:
我的理解:props是组件之间数据,格式,函数等任何数据的传递,state是一个组件的定义的属性。 可以用props传递state。
Step1:Break the UI into Component 层
首先是分析需求,用画图来帮助思考的行为。给每个组件起名字要适合它们的特点。
根据单一责任原理,一个组件理论上只做一件事情。随着组件的成长,应该把它拆分为更小的子组件。
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
先把架子搭起来。整体的层次hi.er.archy。
最简洁的方法:先建立一个包括data model和render UI但没有交互行为的版本。
因为建立一个静态版本只需大量的type不需要思考,而交互设计则需要大量思考而type较少。
⚠️ state是为交互行为服务的,建立静态版本无需使用state.
为了建立一个静态版本的app,需要渲染数据模块,renders your data model。
应当建立那种可以反复使用其他的组件和使用props传递data的组件。
props是把数据从父传给子的一个办法。
选择建设组件的方式:
小的app:设计原则是从top->down, 大的复杂的app则相反bottom-up。
最后,用reusable的组件渲染你的数据model。作为静态版本,组件中只有render()方法。
FilterableProductTable组件处于the top of the hierarchy, 它用props传递data model。
forEach方法
mySet.forEach(function callback(value1, value2, Set){
//your iterator
}[, thisArg])
callback: Function to execute for each element
value1,value2: The value contained in the current position in the Set.
当set对象no key时,value2也是值。这样Array也可以使用forEach方法。
set: The set object that's being traversed.
thisArg: Value to use as this when executing callback
对集合中的每个原素执行提供的callback函数一次,它不返回任何值。
用到了关键字key.
Step3: Identify the minimal(but complete)Representation of UI State 识别最小的UI state
为了正确建立app,首先,思考哪些是最小变化的state。这里也提出dry 原则。
找出绝对的代表最小state。
本例:
- 原始的产品列表
- 用户输入的搜索文本
- checkbook的值
- 产品检索后的列表
思考:找出state。问3个问题:
- Is it passed in from a parent via props? If so, it isn't state. (存在于层次较高的组件)
- Does it remain unchanged over time? If so, it isn't state. (值肯定是会改变)
- Can you compute it based on any other state or props in you component? If so, it isn't state.(不会参与其他state/props的计算)
最后,我的的state是:
The search text that user has entered, the value of the checkbox
Step4: Identify where your state should live
识别这些state存在于哪个组件。
记住:React是关于单向数据流动的组件层级关系。对应新手来说,不容易理解组件应该拥有什么state。所以跟随以下思考步骤:
- 识别每个组件基于state渲染了什么。
- 找到一个共同的拥有者组件,在层级上比其他组件更高的组件需要state。
- 在层级上更高的组件应当拥有state.
- 如果不能找到一个组件让它适合拥有一个state, 则创建一个新的组件来持有这个state并且这个新组件在层级上应该高于这个普通组件。
- ProductTable 拥有检索产品列表(基于state和SearchBar)
- The common owner 组件 是 FilterableProductTable
- 检索文体和checked 值在FilterableProductTable中是可以的
首先:增加一个实例特性instance property this.state={filterText: '', inStockOnly: false}
其次:传递filterText和inStockOnly给ProductTable 和 SearchBar作为一个prop.
最后:使用这些props来检索在ProductTable中的Rows
indexOf(searchElement[, fromIndex]) :
returns the first index at which a given element can be found in the string/array, or -1 if it is not present.
indexOf()方法返回调用String/Array对象中第一次出现的指定值的索引,如果没有找到则返回-1.⚠️indexOf是用严格模式,区分大小写字母。如:
"Blue Whale".indexOf("blue") //returns -1
console.log(beasts.indexOf('bison', 2)); 输出4 ,fromIndex开始搜索的位置,如果这个索引大于等于数组的长度,返回-1
var str = "To be, or not to be, that is the question.";
var count = 0;
var pos = str.indexOf('e'); // 相当于Ruby中的 index方法,Ruby没有找到返回nil。
while (pos !==-1 ) {
count++;
pos = str.indexOf('e', pos + 1);
}
console.log(count); //display 4
Step5: Add Inverse Data Flow
第4步,当函数的props和state顺着组件的层级hierarchy向下流动时,app正确地渲染。
现在,到了支持数据逆向流动的步骤。表格组件需要更新FilterableProductTable中的state。
当用户改变form时,我们根据用户的输入input更新state。组件只能更新自己的state。所以FilterableProductTable会把更新的回调行为传给SearchBar。我们使用onChange事件。这个由FilterableProductTable传递的回调行为叫做setState. 之后app就会更新state了,之后则是因为state的改变,导致state data沿着组件的层级顺流而下,app渲染各个组件。
看起来复杂,但代码只有几行。并且这种清晰的数据流动遍及整个app。
❌:上层组件传递的对state操作的方法的name,不能和传入的组件中声明的方法同名,这样会造成控制组件中的某个输入功能无效。本例子:
传入的名字:handleInStock,组件中自身也在constructor中命名一个handleInStock自然会无效。
⚠️ :return关键字在Ruby和JavaScript中的理解。
Ruby中return可以用于结束当前循环并返回值 (当然还有别的地方也用return),而在JS中return用于结束函数执行并返回一个指定值,箭头函数也是函数。
记住:代码是用来读的而不是写的。就是可读行,可理解行强。清楚的模块很容易读和理解。
当建立大的app的组件时,每个功能都用独立的组件,因为会反复使用组件,所以整体上代码会shrink收缩。