UNPKG

14.1 kBMarkdownView Raw
1Composi
2=======
3
4Contents
5--------
6- [Installation](../README.md)
7- JSX
8- [Hyperx](./hyperx.md)
9- [Hyperscript](./hyperscript.md)
10- [Functional Components](./functional-components.md)
11- [Mount and Render](./render.md)
12- [Components](./components.md)
13- [State](./state.md)
14- [Lifecycle Methods](./lifecycle.md)
15- [Events](./events.md)
16- [Styles](./styles.md)
17- [Unmount](./unmount.md)
18- [Third Party Libraries](./third-party.md)
19- [Deployment](./deployment.md)
20
21JSX
22----
23
24JSX provides a concise and convenient way to define markup to be created. Often people erroneously call JSX HTML. It is not HTML. It is in fact a type of XML. When you build your project, Babel takes the JSX code and converts it into hyperscript functions. In the case of a Composi project, it is set up to tell Babel to use Composi's hyperscript function for that transformation in the project's [.babelrc](https://babeljs.io/docs/usage/babelrc/) file.
25
26
27XML
28---
29
30Since JSX is a type of XML, tags need to follow XML rules. This means that all tags must be closed. In HTML 5 you can have self-closing tags, such as img, br, hr, input, etc.
31
32```html
33<img src='kitten.png'>
34<br>
35<hr>
36<input type='text' value='Cute Kitten'>
37```
38
39To use the above tags in JSX, they would need to be closed with a forward slash:
40
41```html
42<img src='kitten.png' />
43<br />
44<hr />
45<input type='text' value='Cute Kitten' />
46```
47
48Although some "purists" complain that JSX is mixing HTML into JavaScript, this is not completely true. JSX is just a DSL that describes the JavaScript functions that will be created to produce the elements. It is in fact very similar to a now abandoned effort to enable using XML in JavaScript called [E4X](https://developer.mozilla.org/en-US/docs/Archive/Web/E4X).
49
50If you read the E4X documentation, you will recognize the similarities to JSX. E4X was an attempt by Mozilla to enable the creation of DOM nodes without using string concatenation. Unfortunately E4X never took off. The introduction of template literals and tagged template literals solved some of the problems E4X was trying to address. However, the shortcoming of template literals is that the the markup is defined as strings. This means IDEs cannot understand the markup in a template literal. JSX does not have this limitation. As such text editors and IDEs provide great tooling to make using JSX productive.
51
52JSX Attributes
53--------------
54
55Unlike using JSX with React, Composi does not require that you use `className` instead of `class`. The following JSX would be invalid for React, but is the stardard for Composi:
56
57```javascript
58// Use class attribute as normal:
59function header(data) {
60 return (
61 <header>
62 <h1 class='title'>{data.title}</h1>
63 <h2 class='subtitle'>{data.subtitle}</h2>
64 </header>
65 )
66}
67```
68
69### Partial Attributes
70
71JSX does not support partial attribute values. The following code will not work:
72
73```javascript
74function userList(users) {
75 // The partial class defined below will not work:
76 return (
77 <ul>
78 {
79 users.map(user => (
80 <li class='currently-{user.employed ? "employed" : "unemployed"}'>{user.name}</li>)
81 )
82 }
83 </ul>
84 )
85}
86```
87
88The above JSX for the class value will generate an error. Instead you need to make the entire attribute value an evaluation. To do this, get replace the attribute value quotes with curly braces. Then use a template literal to output the results:
89
90```javascript
91function userList(users) {
92 // Calculate the entire class value inside curly braces:
93 return (
94 <ul>
95 {
96 users.map(user => <li class={`currently-${user.employed ? "employed" : "unemployed"}`}>{user.name}</li>)
97 }
98 </ul>
99 )
100}
101```
102
103**Note** When evaluating attribute values, never use quotes around the evaluation as this will prevent the evaluation from happening. Just use curly braces.
104
105Custom Tags
106------------
107
108Although JSX makes it easy to create standard HTML elements, it also allows you to create custom tags. These are not the same as [custom element](). There may be a few, higher level similarities between these two. But fundamentaly they are for different purposes.
109
110A custom tag is really just a function that returns some JSX. When you want to use it in other JSX, you do so as a tag. Functions for custom tags must start with an uppercase letter, with no exception. When your JSX consists of many different parts, it makes sense to break it down into modular pieces.
111
112Suppose we have a component with a render function like this:
113
114```javascript
115const fruitsList = new Component({
116 container: '#fruit-list',
117 state: fruits,
118 render: (fruits) => (
119 <ul class='list'>
120 {
121 fruits.map(fruit =>
122 <li>
123 <div>
124 <h3>{fruit.title}</h3>
125 <h4>{fruit.subtitle}</h4>
126 </div>
127 <aside>
128 <span class='disclosure'></span>
129 </aside>
130 </li>
131 )
132 }
133 </ul>
134 )
135})
136```
137
138Custom Tags with Spread Operator
139--------------------------------
140
141Looking at the above markup, we could break it up a bit to make it easier to read and reuse. To do this we'll define two functions to return markup. As mentioned earlier functions for custom tags must start with an uppercase letter. Lets break this up into two subcomponents:</p>
142
143```javascript
144// Define custom tag for fruit:
145function FruitItem({fruit}) {
146 return (
147 <div>
148 <h3>{fruit.title}</h3>
149 <h4>{fruit.subtitle}</h4>
150 </div>
151 )
152}
153
154// Define custom tag for disclosure tag:
155function Disclosure() {
156 return (
157 <aside>
158 <span class='disclosure'></span>
159 </aside>
160 )
161}
162```
163
164<p>In order to pass in the data, we need to encose the data parameter in curly braces. This is how props are passed to tags in JSX. When we actually use a custom tag, we need to use another convention with curly braces: double braces and a spread operator for destructuring assignment. This will pass each property of the object to the JSX function so that they can be accessed. It looks like this:
165
166```javascript
167 const data = {
168 prop1: 'Whatever',
169 prop2: 'More of the Same'
170 }
171 // Pass object with destructing to tag:
172 <MyTag {...{data}} />
173```
174
175Remember that custom tags need to be closed with a forward slash at the end.
176
177Below is how we can break our JSX into custom tags:
178
179```jsx
180function ListItem({fruit}) => (
181 <div>
182 <h3>{fruit.title}</h3>
183 <h4>{fruit.subtitle}</h4>
184 </div>
185)
186
187// No need for props here,
188// we're just returning markup:
189function Disclosure() => (
190 <aside>
191 <span class='disclosure'></span>
192 </aside>
193)
194
195// Now that we have some custom tags, we can use them as follows:
196
197const fruitsList = new Component({
198 container: '#fruit-list',
199 state: fruits,
200 // Use spread operator on ListItem:
201 render: (fruits) => (
202 <ul class='list'>
203 {
204 fruits.map(fruit => (
205 <li>
206 <ListItem {...{fruit} />
207 <Disclosure />
208 </li>
209 )
210 }
211 </ul>
212 )
213})
214```
215
216This results in cleaner code that is easier to read and maintain.
217
218One Tag to Rule Them All
219------------------------
220Because of the way JSX works, there must always be one enclosing tag for all the other tags you wish to create. You cannot return a group of siblings. They need to be contained in another tag. For example, suppose you wanted to create some list items to insert in a list:
221
222```javascript
223// This will not compile:
224const badJSX = new Component({
225 container: '#list',
226 render: () => (
227 <li>One</li>
228 <li>Two</li>
229 <li>Three</li>
230 )
231})
232```
233
234The above code will not build. Instead you need to create the entire list like this and insert it in some higher element as the container:
235
236```javascript
237const goodJSX = new Component({
238 container: '#listDiv',
239 render: () => (
240 <ul>
241 <li>One</li>
242 <li>Two</li>
243 <li>Three</li>
244 </ul>
245 )
246})
247```
248
249Fragment Tag
250------------
251
252As of version 1.5.0, Composi also supports a special Fragment tag. This allows you to group a number a siblings together instead of needing to enclose them in an html tag. The Fragment tag will be the parent, however it will not be converted into a tag in the DOM. Instead its children will become the children of whatever the Fragment gets inserted into. This is similar to how document fragments work. However, this is not an actual document fragment. The Fragment tag must always start with a capital F:
253
254```javascript
255function Title() {
256 return (
257 <Fragment>
258 <h1>Main Title</h1>
259 <h2>Secondary Title</h2>
260 </Fragment>
261 )
262}
263```
264
265Let's look at the previous list example to see how we can use Fragments to make it more manageable. Before we can use the Fragment tag, we need to import it into our project:
266
267```javascript
268import {h, Fragment, Component} from 'composi'
269
270class List extends Component {
271 container = '#listDiv'
272 state = ['Apples', 'Oranges', 'Bananas']
273 render(items) {
274 function ListItems({items}) {
275 return (
276 <Fragment>
277 {
278 items.map(item => <li>{item}</li>)
279 }
280 </Fragment>
281 )
282 }
283 return (
284 <ul>
285 <ListItems items={this.state}>
286 <ListItems>
287 </ul>
288 )
289 }
290}
291// Instantiate a new List:
292new List()
293```
294
295Because Fragment just returns their children, if you nest them, when you return them their children will be flattens. Notice what the following exaple returns:
296
297```javascript
298import {h, render, Fragment} from 'composi'
299
300const letters = ['A', 'B', 'C', 'D', 'E', 'F']
301function Items({letters}) {
302 return (
303 <main>
304 <Fragment>
305 <span>{letters[0]}</span>
306 <span>{letters[1]}</span>
307 <Fragment>
308 <span>{letters[2]}</span>
309 <span>{letters[3]}</span>
310 <Fragment>
311 <span>{letters[4]}</span>
312 <span>{letters[5]}</span>
313 </Fragment>
314 </Fragment>
315 </Fragment>
316 </main>
317 )
318}
319
320render(<Items letters={letters}/>, document.body)
321// This will create the following:
322<main>
323 <span>A</span>
324 <span>B</span>
325 <span>C</span>
326 <span>D</span>
327 <span>E</span>
328 <span>F</span>
329</main>
330```
331
332Components with Same Container
333------------------------------
334
335Components do not have to have unique container elements. Multiple components can be rendered in the same container element. Their order in the DOM will be dependent on their order in execution.
336
337Using SVG Sprite Icons
338----------------------
339
340Often developers use SVG sprite sheets for icons in their apps. Here are some articles about how this works: [Icon System with SVG Sprites](https://css-tricks.com/svg-sprites-use-better-icon-fonts/), [An Overview of SVG Sprite Creation Techniques](https://24ways.org/2014/an-overview-of-svg-sprite-creation-techniques/), [How to Implement Cross-Browser SVG Sprites](https://webdesign.tutsplus.com/tutorials/how-to-implement-cross-browser-svg-sprites--cms-22427)
341
342### SVG 1 and SVG 2
343
344SVG 1.0 uses the `xlink:ref` attribute to link to an icon id in an SVG sprite sheet image. Unfortunately, JSX does not support namespaced properties on SVG. To get arround this limitation, Composi lets you use a custom property: `xlink-href` in your SVG icons. At render time this gets converted to the correct form as `xlink:href`. Although `xlink:href` is currently listed as deprecated for Firefox, Chrome and Edge, it is widely supported on older browsers. In fact, it is currenlty the only way to implement SVG icons on macOS and iOS. If you are not targetting macOS and iOS and do not care about older versions of IE, Firefox and Chrome, you can use the new syntax for SVG 2.0. This is a simple `href` property.
345
346
347Below is an SVG Twitter image that we will use as the basis for a series of SVG icons of different colors. Notice that we have a path with the id of "shape-twitter". We'll use that id to pull that path into our icon.
348
349```html
350<svg class="hide" style="display:none;">
351 <g id="shape-codepen">
352 <path id="shape-twitter" d="M100.001,17.942c-3.681,1.688-7.633,2.826-11.783,3.339
353 c4.236-2.624,7.49-6.779,9.021-11.73c-3.965,2.432-8.354,4.193-13.026,5.146C80.47,10.575,75.138,8,69.234,8 c-11.33,0-20.518,9.494-20.518,21.205c0,1.662,0.183,3.281,0.533, 4.833c-17.052-0.884-32.168-9.326-42.288-22.155
354 c-1.767,3.133-2.778,6.773-2.778,10.659c0,7.357,3.622,13.849,9.127, 17.65c-3.363-0.109-6.525-1.064-9.293-2.651
355 c-0.002,0.089-0.002,0.178-0.002,0.268c0,10.272,7.072,18.845,16.458,20.793c-1.721,0.484-3.534,0.744-5.405,0.744
356 c-1.322,0-2.606-0.134-3.859-0.379c2.609,8.424,10.187,14.555,19.166,14.726c-7.021,5.688-15.867,9.077-25.48,9.077
357 c-1.656,0-3.289-0.102-4.895-0.297C9.08,88.491,19.865,92,31.449,92c37.737,0,58.374-32.312,58.374-60.336
358 c0-0.92-0.02-1.834-0.059-2.743C93.771,25.929,97.251,22.195,100.001,17.942L100.001,17.942z"></path>
359 </g>
360</svg>
361```
362
363This image needs to be loaded into the document so that it is exposed globally. Since we put `style="display:none:"` on the SVG tag, we don't have to worry about it showing up anywhere. To use this, we can do the following:
364
365```javascript
366const icons = new Component({
367 container: 'section',
368 render: (data) => {
369 return (
370 <div>
371 <svg viewBox="0 0 100 100" class="icon--shape-twitter-1">
372 <use xlink-href="#shape-twitter"></use>
373 </svg>
374
375 <svg viewBox="0 0 100 100" class="icon--shape-twitter-2">
376 <use xlink-href="#shape-twitter"></use>
377 </svg>
378
379 <svg viewBox="0 0 100 100" class="icon--shape-twitter-3">
380 <use xlink-href="#shape-twitter"></use>
381 </svg>
382
383 <svg viewBox="0 0 100 100" class="icon--shape-twitter-4">
384 <use xlink-href="#shape-twitter"></use>
385 </svg>
386 </div>
387 )
388 }
389})
390icons.update()
391```
392
393Notice that we gave each SVG tag a unique class. We can use these to give each icon a different color:
394
395```css
396svg * {
397 transition: all .5s ease-out;
398}
399.shape-twitter-1 {
400 fill: #000
401}
402.shape-twitter-2 {
403 fill: #55ACEE
404}
405.shape-twitter-3 {
406 fill: #ff0000;
407}
408.shape-twitter-4 {
409 fill: #00aa00;
410}
411.shape-twitter-1:hover,
412.shape-twitter-2:hover,
413.shape-twitter-3:hover,
414.shape-twitter-4:hover {
415 fill: #0000ff;
416}
417```