UNPKG

12.4 kBMarkdownView Raw
1Composi
2=======
3
4Contents
5--------
6- [Installation](../README.md)
7- [JSX](./jsx.md)
8- [Hyperx](./hyperx.md)
9- [Hyperscript](./hyperscript.md)
10- [Functional Components](./functional-components.md)
11- [Mount, Render and Unmount](./render.md)
12- Components
13 - [Class Component](#Class-Component)
14 - [Exteding the Class Component](#Extending-the-Class-Component)
15 - [State](#State)
16 - [Events](#Events)
17 - [Styles](#Styles)
18 - [Lifecylce Methods](#Lifecylce-Methods)
19 - [Trigger Initial Render With State](#Trigger-Initial-Render-with-State)
20 - [Querying the DOM](#Querying-the-DOM)
21 - [SSR & Hydration](#SSR-&-Hydration)
22- [State](./state.md)
23- [Lifecycle Methods](./lifecycle.md)
24- [Events](./events.md)
25- [Styles](./styles.md)
26- [State Management with DataStore](./data-store.md)
27- [Unmount](./unmount.md)
28- [Third Party Libraries](./third-party.md)
29- [Deployment](./deployment.md)
30- [Differences with React](./composi-react.md)
31
32## Class Component
33
34Composi is a library that provides a rich and powerful solution for components. This is provided by the Component class. To use it, you'll first need to create a project. After creating a project (see [Create a New Project](../README.md)), import both `h` and `Component` into your `app.js` file:
35
36```javascript
37import {h, Component} from 'composi'
38```
39
40You create a new or custom component by extending the Component class. Components have a number of properties that you can use to make your component useful. Below is a general list of properties. There are differences in how an instance of the Component class, or an extension of it, use these.
41
421. container - the element in the DOM in which the component will be rendered. Multiple components can share the same container. In such a case they will be appended to the container one after the other in the order you first render them or set their initial state.
432. render - a function that returns markup for the element. This function may optionally take some data that it uses to create dynamic content for the component. You may want to use inline events in your markup to capture user interactions. Read the [documentation for events](./events.md) for more information.
443. state - some data that the component will use when rendering. This could be primitive types, or an object or array.
454. Lifecycle methods - these allow you to do things during the lifecycle of the component.
46
47## Extending the Class Component
48
49By extending Component, you can create a custom, reusable component. This is how you would be able to create multiple versions of the same component on the page. You would need to provide only the container element and data to render. If you want to use inline events, you would want to create and extension of Component because then you can define all your actions directly on the component. To learn more about adding events to components, read the [documentation](./events.md).
50
51Below we are going to create a list component to create multiple lists with different datasets:
52
53```javascript
54import {h, Component} from 'composi'
55
56// Three arrays of data for three different components:
57const fruits = [
58 {
59 name: 'Apple',
60 quantity: 2
61 },
62 {
63 name: 'Orange',
64 quantity: 3
65 },
66 {
67 name: 'Banana',
68 quantity: 1
69 }
70]
71
72const pets = [
73 {
74 name: 'cat',
75 quantity: 1
76 },
77 {
78 name: 'dog',
79 quantity: 1
80 },
81 {
82 name: 'turtle',
83 quantity: 0
84 }
85]
86
87const tools = [
88 {
89 name: 'hammer',
90 quantity: 1
91 },
92 {
93 name: 'nails',
94 quantity: 100
95 },
96 {
97 name: 'screw driver',
98 quantity: 1
99 }
100]
101
102// Extend Component:
103class List extends Component {
104 constructor(opts) {
105 super(opts)
106 // Render all instances in the body:
107 this.container = 'body'
108 }
109
110 render(data) {
111 return (
112 <ul>
113 {
114 data.map(item => (
115 <li>
116 <div>Name: {item.name}</div>
117 <div>Quanity: {item.quantity}</div>
118 </li>))
119 }
120 </ul>
121 )
122 }
123}
124
125// Create three instances of List.
126// Then assign them state to render them.
127const fruitsList = new List()
128fruitsList.state = fruits
129
130const petsList = new List()
131petsList.state = pets
132
133const toolsList = new List()
134toolsList.state = tools
135```
136
137The above code would render three lists in the document body.
138
139## State
140
141In the above examples our components were stateless. Whatever data they needed, we passed it to them using their `update` methods. But you can define a component with state. The advantage is that when you change the state, the component automatically updates. Learn more about state by reading the [documentation](./state.md).
142
143## Events
144
145Components can handle events in two ways, inline or as event listeners. Learn more about adding events to component by reading the [documentation](./events.md).
146
147## Styles
148
149There are two ways to define styles for a component: a component style tag or a virtual stylesheet scoped to a component. This makes the component easier to reuse. Learn more about defining styles for components by reading the [documentation](./styles.md).
150
151## Lifecylce Methods
152
153Components have five lifecycle methods:
154
1551. componentWillMount
1562. componentDidMount
1573. componentWillUpdate
1584. componentDidUpdate
1595. componentWillUnmount
160
161Learn more about using lifecycle methods with components by reading the [documentation](./lifecycle.md).
162
163About Component Instantiation
164-----------------------------
165
166When you create a stateless component, you'll need to pass it some data with its `update` method to get it to render in the DOM. Of course, you counld make a component that does not need any data passed to it because it is not rendering any data. Below we show both approaches:
167
168```javascript
169// Render title component, no data needed:
170const title = new Component({
171 container: 'header',
172 // Define render function that returns state markup:
173 render: () => <h1>This is a Title!</h1>
174})
175// Render component without data:
176titleComponent.update()
177
178// Render the fruitList component with fruits data:
179const fruitList = new Component({
180 container: 'body',
181 // Define render function that consumes fruit data:
182 render: (fruits) => (
183 <ul class='list'>
184 {
185 fruits.map(fruit => <li>{fruit}</li>)
186 }
187 </ul>
188 )
189})
190// Render component with fruit data:
191fruitList.update(fruits)
192```
193### Creating a Stateful Component
194When you create a class by extending Component you can set default state in the constructor. When you create a new instance of such a class, the state gets set. This triggers the creation of a virtual DOM, Composi runs a diff, and then patches the DOM. This results in the automatic rendering and insertion of the component in the DOM without having to run `update` on the component instance. Examine the example below:
195
196```javascript
197import {h, Component} from 'composi'
198
199class Clock extends Component {
200 constructor(opts) {
201 super(opts)
202 this.state = {time: Date.now()}
203 this.styles = {
204 'p > span': {
205 fontFamily: 'Monospace'
206 }
207 }
208 }
209 render() {
210 let time = this.state.time
211 const angle = (time) => 2 * Math.PI * time / 60000
212 return (
213 <li>
214 <div>
215 <svg viewBox="0 0 100 100" width="150px">
216 <circle cx="50" cy="50" r="45" fill="blue" />
217 <line
218 stroke-width="2"
219 x1="50"
220 y1="50"
221 x2={ 50 + 40 * Math.cos(angle(time)) }
222 y2={ 50 + 40 * Math.sin(angle(time)) }
223 stroke="white"
224 />
225 </svg>
226 <p>The time: <span>{ new Date(time).getHours() > 12 ? new Date(time).getHours() -12 : new Date(time).getHours() }:{ new Date(time).getMinutes() }:{ new Date(time).getSeconds() < 10 ? '0' + new Date(time).getSeconds() : new Date(time).getSeconds()} { new Date(time).getHours() > 12 ? 'PM' : 'AM' }</span></p>
227 </div>
228 </li>
229 )
230 }
231
232 tick() {
233 this.setState({time: new Date()})
234 }
235
236 componentDidMount() {
237 this.timeID = setInterval(() => { this.tick() }, 1000)
238 }
239}
240const clock = new Clock({
241 container: '#clock'
242})
243```
244
245## Trigger Initial Render With State
246
247When you create a component instance, you can trigger its render by setting state on the instance. When you do, no need to use `update` on it:
248
249```javascript
250// Define component:
251const hello = new Component({
252 container: 'body',
253 render: (name) => <h1>Hello, {name}!</h1>
254})
255
256// Setting state on the Component instance will cause the component to render to the DOM into the body tag:
257hello.state = 'World'
258```
259
260## Querying the DOM
261
262### this.element
263
264Composi does not have a `ref` property like React. However, every component has an `element` property, which is the base element you define in the component's `render` function. Let's look at the following example:
265
266```javascript
267const List extends Component {
268 constructor(props) {
269 super(props)
270 this.container = 'body',
271 }
272 render(data) {
273 return (
274 <ul class='list'>
275 {
276 data.map(item => <li>{item}</li>)
277 }
278 </ul>
279 )
280 }
281}
282const list = new List()
283// Render list with data:
284list.update(['One','Two','Three'])
285```
286
287The above component instance `list` has a property `element`. In this case its value will be `<ul class="list"></ul`> We can search for any of the list's child elements using the `element` property as the starting point. This gives you more precise, scoped queries:
288
289```javascript
290// Get the text of the second list item:
291const text = list.element.children[1].textContent
292
293// Get the last list item,
294// then set an event on it:
295const lastItem = list.element.querySelector('li:last-of-type')
296lastItem.addEventListener('click', (e) => alert(e.textContent.trim()))
297```
298For components with complex descendent structure you can use the `element` property for access. You can also access the `element` property from the Component extension's `this` keyword. Notice how we do this in the `update` method below:
299
300```javascript
301import {h, Component} from 'composi'
302
303class Person extends Component {
304 constructor(props) {
305 super(props)
306 this.container = 'section'
307 }
308 render(person) {
309 return (
310 <div class='person-component'>
311 <h3>{person.name}</h3>
312 <p>
313 <label for="name">New Name:</label>
314 <input type="text" />
315 <button onclick={() => this.updateName()}>Update</button>
316 </p>
317 </div>
318 )
319 }
320 updateName() {
321 // Get input through 'element' property:
322 const input = this.element.querySelector('input')
323 const name = input.value || 'unknown'
324 this.setState({name})
325 }
326}
327const person = new Person()
328person.state = {name: 'Joe'}
329```
330
331## SSR & Hydration
332
333You can use whatever server-side solution to pre-render the html for your document. Then after page loads, you can let Composi take over parts of the document as class components. To do this you need to tell the component what element in the DOM to hydrate. You do that with the `hydrate` property.
334
335Let's take a look at how we might do this. Suppose on the server we output some markup as follows:
336
337```html
338<body>
339 <article>
340 <ul id="specialList">
341 <li>Apples</li>
342 <li>Oranges</li>
343 <li>Bananas</li>
344 </ul>
345 </article>
346</body>
347```
348
349When the page first loads this will be the default content. In fact, if the JavaScript did not load or failed with an exception, the user would see this content. If we want to replace the static content with a dynamic list, we can define the list component like this:
350
351```javascript
352class Liste extends Component {
353 render(data) {
354 return (
355 <ul id="specialList">
356 {
357 fruits.map(fruit => <li>{fruit}</li>)
358 }
359 </ul>
360 )
361 }
362}
363
364// Create instance of List:
365const list = new List({
366 // Designate container to render in:
367 container: 'article',
368 // Tell Composi to hydrate the list with an id of 'specialList':
369 hydrate: 'specialList'
370 // Set state, render the component and replace state nodes:
371 state: ['Stawberries', 'Peaches', 'Blueberries']
372})
373```
374
375With the above code, even though the server sent a static list to the browser, at load time Composi will replace it with the dynamic component of the same id in the same container element.
376
377**Note:** When implementing serve-side rendering and component hydration, it's best to use ids for the parts you want to hydrate. That way it's easier for Composi to identify unique parts of the static HTML to take over.
378