├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── READINGS.md ├── README.md ├── SUMMARY.md ├── anti-patterns ├── 01.props-in-initial-state.md ├── 02.findDOMNode.md ├── 03.mixins.md ├── 04.setState-in-componentWillMount.md ├── 05.mutating-state.md ├── 06.using-indexes-as-key.md ├── 07.spreading-props-dom.md └── README.md ├── book.json ├── coding-style └── README.md ├── docs ├── CONTRIBUTING.md ├── LICENSE.txt ├── READINGS.html ├── anti-patterns │ ├── 01.props-in-initial-state.html │ ├── 02.findDOMNode.html │ ├── 03.mixins.html │ ├── 04.setState-in-componentWillMount.html │ ├── 05.mutating-state.html │ ├── 06.using-indexes-as-key.html │ ├── 07.spreading-props-dom.html │ └── index.html ├── coding-style │ └── README.md ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-edit-link │ │ └── plugin.js │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-highlight │ │ ├── ebook.css │ │ └── website.css │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── gotchas │ ├── 01.pure-render-checks.html │ ├── 02.synthetic-events.html │ └── index.html ├── index.html ├── package.json ├── patterns │ ├── 18.conditionals-in-jsx.html │ ├── 19.async-nature-of-setState.html │ ├── 20.dependency-injection.html │ ├── 21.context-wrapper.html │ ├── 22.event-handlers.html │ ├── 23.flux-pattern.html │ ├── 24.one-way-data-flow.html │ ├── 25.presentational-vs-container.html │ ├── 26.third-party-integration.html │ ├── 27.passing-function-to-setState.html │ ├── 28.decorators.html │ ├── 29.feature-flags-using-redux.html │ ├── 30.component-switch.html │ ├── 31.reaching-into-a-component.html │ ├── 32.list-components.html │ ├── 33.format-text-via-component.html │ └── 34.share-tracking-logic.html ├── perf-tips │ ├── 01.shouldComponentUpdate-check.html │ ├── 02.pure-component.html │ ├── 03.reselect.html │ └── index.html ├── search_index.json ├── styling │ ├── 01.stateless-ui-components.html │ ├── 02.styles-module.html │ ├── 03.style-functions.html │ ├── 04.using-npm-modules.html │ ├── 05.base-component.html │ ├── 06.layout-component.html │ ├── 07.typography-component.html │ ├── 08.HOC-for-styling.html │ └── index.html └── ux-variations │ ├── 01.composing-variations.html │ ├── 02.toggle-ui-elements.html │ ├── 03.HOC-feature-toggles.html │ ├── 04.HOC-props-proxy.html │ ├── 05.wrapper-components.html │ ├── 06.display-order-variations.html │ └── index.html ├── gotchas ├── 01.pure-render-checks.md ├── 02.synthetic-events.md └── README.md ├── package.json ├── patterns ├── 18.conditionals-in-jsx.md ├── 19.async-nature-of-setState.md ├── 20.dependency-injection.md ├── 21.context-wrapper.md ├── 22.event-handlers.md ├── 23.flux-pattern.md ├── 24.one-way-data-flow.md ├── 25.presentational-vs-container.md ├── 26.third-party-integration.md ├── 27.passing-function-to-setState.md ├── 28.decorators.md ├── 29.feature-flags-using-redux.md ├── 30.component-switch.md ├── 31.reaching-into-a-component.md ├── 32.list-components.md ├── 33.format-text-via-component.md └── 34.share-tracking-logic.md ├── perf-tips ├── 01.shouldComponentUpdate-check.md ├── 02.pure-component.md ├── 03.reselect.md └── README.md ├── styling ├── 01.stateless-ui-components.md ├── 02.styles-module.md ├── 03.style-functions.md ├── 04.using-npm-modules.md ├── 05.base-component.md ├── 06.layout-component.md ├── 07.typography-component.md ├── 08.HOC-for-styling.md └── README.md └── ux-variations ├── 01.composing-variations.md ├── 02.toggle-ui-elements.md ├── 03.HOC-feature-toggles.md ├── 04.HOC-props-proxy.md ├── 05.wrapper-components.md ├── 06.display-order-variations.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | node_modules 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | > Your contributions are heartily ♡ welcome. (✿◠‿◠) 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public licenses. 379 | Notwithstanding, Creative Commons may elect to apply one of its public 380 | licenses to material it publishes and in those instances will be 381 | considered the "Licensor." Except for the limited purpose of indicating 382 | that material is shared under a Creative Commons public license or as 383 | otherwise permitted by the Creative Commons policies published at 384 | creativecommons.org/policies, Creative Commons does not authorize the 385 | use of the trademark "Creative Commons" or any other trademark or logo 386 | of Creative Commons without its prior written consent including, 387 | without limitation, in connection with any unauthorized modifications 388 | to any of its public licenses or any other arrangements, 389 | understandings, or agreements concerning use of licensed material. For 390 | the avoidance of doubt, this paragraph does not form part of the public 391 | licenses. 392 | 393 | Creative Commons may be contacted at creativecommons.org. 394 | -------------------------------------------------------------------------------- /READINGS.md: -------------------------------------------------------------------------------- 1 | # Related Links 2 | - [React in Patterns by krasimir](https://github.com/krasimir/react-in-patterns) 3 | - [React Patterns by planningcenter](https://github.com/planningcenter/react-patterns) 4 | - [reactpatterns.com](https://github.com/chantastic/reactpatterns.com) 5 | - [10 React Mini-patterns](https://hackernoon.com/10-react-mini-patterns-c1da92f068c5) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [React Bits](https://vasanthk.gitbooks.io/react-bits) 2 | 3 | 有关React,你需要知道的一切 4 | 5 | **Gitbook format**: https://hateonion.github.io/react-bits-CN/ 6 | 7 | 原Repo地址 https://github.com/vasanthk/react-bits 8 | 9 | > Your contributions are heartily ♡ welcome. (✿◠‿◠) 10 | > 新司机上路, 欢迎大家随时提issue和pr进行指正 11 | 12 | - Design Patterns and Techniques 13 | - ✔ ️[Conditional in JSX](./patterns/18.conditionals-in-jsx.md) 14 | - ✔ [Async Nature Of setState()](./patterns/19.async-nature-of-setState.md) 15 | - ✔ [Dependency Injection](./patterns/20.dependency-injection.md) 16 | - ✔ [Context Wrapper](./patterns/21.context-wrapper.md) 17 | - ✔ [Event Handlers](./patterns/22.event-handlers.md) 18 | - ✔ [Flux Pattern](./patterns/23.flux-pattern.md) 19 | - ✔ [One Way Data Flow](./patterns/24.one-way-data-flow.md) 20 | - ✔ [Presentational vs Container](./patterns/25.presentational-vs-container.md) 21 | - ✔ [Third Party Integration](./patterns/26.third-party-integration.md) 22 | - ✔ [Passing Function To setState()](./patterns/27.passing-function-to-setState.md) 23 | - ✔ [Decorators](./patterns/28.decorators.md) 24 | - ✔ [Feature Flags](./patterns/29.feature-flags-using-redux.md) 25 | - ✔ [Component Switch](./patterns/30.component-switch.md) 26 | - ✔ [Reaching Into A Component](./patterns/31.reaching-into-a-component.md) 27 | - ✔ [List Components](./patterns/32.list-components.md) 28 | - ✔ [Format Text via Component](./patterns/33.format-text-via-component.md) 29 | - ✔ [Share Tracking Logic](./patterns/34.share-tracking-logic.md) 30 | - Anti-Patterns 31 | - ✔ [Introduction](./anti-patterns/README.md) 32 | - ✔ [Props In Initial State](./anti-patterns/01.props-in-initial-state.md) 33 | - ✔ [findDOMNode()](./anti-patterns/02.findDOMNode.md) 34 | - ✔ [Mixins](./anti-patterns/03.mixins.md) 35 | - ✔ [setState() in componentWillMount()](./anti-patterns/04.setState-in-componentWillMount.md) 36 | - ✔ [Mutating State](./anti-patterns/05.mutating-state.md) 37 | - ✔ [Using Indexes as Key](./anti-patterns/06.using-indexes-as-key.md) 38 | - ✔ [Spreading Props on DOM elements](./anti-patterns/07.spreading-props-dom.md) 39 | - Handling UX Variations 40 | - 👍 [Introduction](./ux-variations/README.md) 41 | - ✔ [Composing UX Variations](./ux-variations/01.composing-variations.md) 42 | - ✔ [Toggle UI Elements](./ux-variations/02.toggle-ui-elements.md) 43 | - ✔ [HOC for Feature Toggles](./ux-variations/03.HOC-feature-toggles.md) 44 | - ✔ [HOC props proxy](./ux-variations/04.HOC-props-proxy.md) 45 | - ✔ [Wrapper Components](./ux-variations/05.wrapper-components.md) 46 | - ✔ [Display Order Variations](./ux-variations/06.display-order-variations.md) 47 | - Perf Tips 48 | - ✔ [Introduction](./perf-tips/README.md) 49 | - ✔ [shouldComponentUpdate() check](./perf-tips/01.shouldComponentUpdate-check.md) 50 | - ✔ [Using Pure Components](./perf-tips/02.pure-component.md) 51 | - ✔ [Using reselect](./perf-tips/03.reselect.md) 52 | - Styling 53 | - ✔ [Introduction](./styling/README.md) 54 | - ✔ [Stateless UI Components](./styling/01.stateless-ui-components.md) 55 | - ✔ [Styles Module](./styling/02.styles-module.md) 56 | - ✔ [Style Functions](./styling/03.style-functions.md) 57 | - ✔ [NPM Modules](./styling/04.using-npm-modules.md) 58 | - ✔ [Base Component](./styling/05.base-component.md) 59 | - ✔ [Layout Component](./styling/06.layout-component.md) 60 | - ✔ [Typography Component](./styling/07.typography-component.md) 61 | - ✔ [HOC for Styling](./styling/08.HOC-for-styling.md) 62 | - Gotchas 63 | - ✔ [Introduction](./gotchas/README.md) 64 | - ✔ [Pure render checks](./gotchas/01.pure-render-checks.md) 65 | - ✔ [Synthetic Events](./gotchas/02.synthetic-events.md) 66 | - [Related Links](./READINGS.md) 67 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * Design Patterns and Techniques 5 | * [Conditional in JSX](./patterns/18.conditionals-in-jsx.md) 6 | * [Async Nature Of setState()](./patterns/19.async-nature-of-setState.md) 7 | * [Dependency Injection](./patterns/20.dependency-injection.md) 8 | * [Context Wrapper](./patterns/21.context-wrapper.md) 9 | * [Event Handlers](./patterns/22.event-handlers.md) 10 | * [Flux Pattern](./patterns/23.flux-pattern.md) 11 | * [One Way Data Flow](./patterns/24.one-way-data-flow.md) 12 | * [Presentational vs Container](./patterns/25.presentational-vs-container.md) 13 | * [Third Party Integration](./patterns/26.third-party-integration.md) 14 | * [Passing Function To setState()](./patterns/27.passing-function-to-setState.md) 15 | * [Decorators](./patterns/28.decorators.md) 16 | * [Feature Flags](./patterns/29.feature-flags-using-redux.md) 17 | * [Component Switch](./patterns/30.component-switch.md) 18 | * [Reaching Into A Component](./patterns/31.reaching-into-a-component.md) 19 | * [List Components](./patterns/32.list-components.md) 20 | * [Format Text via Component](./patterns/33.format-text-via-component.md) 21 | * [Share Tracking Logic](./patterns/34.share-tracking-logic.md) 22 | * Anti-Patterns 23 | * [Introduction](./anti-patterns/README.md) 24 | * [Props In Initial State](./anti-patterns/01.props-in-initial-state.md) 25 | * [findDOMNode()](./anti-patterns/02.findDOMNode.md) 26 | * [Mixins](./anti-patterns/03.mixins.md) 27 | * [setState() in componentWillMount()](./anti-patterns/04.setState-in-componentWillMount.md) 28 | * [Mutating State](./anti-patterns/05.mutating-state.md) 29 | * [Using Indexes as Key](./anti-patterns/06.using-indexes-as-key.md) 30 | * [Spreading Props on DOM elements](./anti-patterns/07.spreading-props-dom.md) 31 | * Handling UX Variations 32 | * [Introduction](./ux-variations/README.md) 33 | * [Composing UX Variations](./ux-variations/01.composing-variations.md) 34 | * [Toggle UI Elements](./ux-variations/02.toggle-ui-elements.md) 35 | * [HOC for Feature Toggles](./ux-variations/03.HOC-feature-toggles.md) 36 | * [HOC props proxy](./ux-variations/04.HOC-props-proxy.md) 37 | * [Wrapper Components](./ux-variations/05.wrapper-components.md) 38 | * [Display Order Variations](./ux-variations/06.display-order-variations.md) 39 | * Perf Tips 40 | * [Introduction](./perf-tips/README.md) 41 | * [shouldComponentUpdate() check](./perf-tips/01.shouldComponentUpdate-check.md) 42 | * [Using Pure Components](./perf-tips/02.pure-component.md) 43 | * [Using reselect](./perf-tips/03.reselect.md) 44 | * Styling 45 | * [Introduction](./styling/README.md) 46 | * [Stateless UI Components](./styling/01.stateless-ui-components.md) 47 | * [Styles Module](./styling/02.styles-module.md) 48 | * [Style Functions](./styling/03.style-functions.md) 49 | * [npm Modules](./styling/04.using-npm-modules.md) 50 | * [Base Component](./styling/05.base-component.md) 51 | * [Layout Component](./styling/06.layout-component.md) 52 | * [Typography Component](./styling/07.typography-component.md) 53 | * [HOC for Styling](./styling/08.HOC-for-styling.md) 54 | * Gotchas 55 | * [Introduction](./gotchas/README.md) 56 | * [Pure render checks](./gotchas/01.pure-render-checks.md) 57 | * [Synthetic Events](./gotchas/02.synthetic-events.md) 58 | * [Related Links](./READINGS.md) 59 | 60 | -------------------------------------------------------------------------------- /anti-patterns/01.props-in-initial-state.md: -------------------------------------------------------------------------------- 1 | # 根据props去初始化state 2 | 3 | 前言: 4 | > 使用props去在getInitialState中生成初始state(或者在constructor中初始化)很容易导致多个数据源的问题, 也会给使用者带来这样的疑问: 我们的真正的数据源到底来自哪? 5 | > 这是因为getInitialState只在组件第一次初始化的时候被调用一次. 6 | 7 | 这样做的危险在于, 有可能组件的props发生了改变但是组件却没有被更新.(见下面的例子) 8 | 新的props的值不会被React认为是更新的数据因为构造器(constructor)或者getInitialState方法在组件创建之后不会再次被调用了,因此组件的state不再会被更新. 9 | 要记住, State的初始化只会在组件第一次初始化的时候发生. 10 | 11 | #### 坏的实践 12 | ```javascript 13 | class SampleComponent extends Component { 14 | // constructor function (or getInitialState) 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | flag: false, 19 | inputVal: props.inputValue 20 | }; 21 | } 22 | 23 | render() { 24 | return
{this.state.inputVal && }
25 | } 26 | } 27 | ``` 28 | #### 好的实践 29 | ```javascript 30 | class SampleComponent extends Component { 31 | // constructor function (or getInitialState) 32 | constructor(props) { 33 | super(props); 34 | this.state = { 35 | flag: false 36 | }; 37 | } 38 | 39 | render() { 40 | return
{this.props.inputValue && }
41 | } 42 | } 43 | ``` 44 | 45 | ### 参考资料: 46 | - [React Anti-Patterns: Props in Initial State](https://medium.com/@justintulk/react-anti-patterns-props-in-initial-state-28687846cc2e) 47 | - [Why is passing the component initial state a prop an anti-pattern?](http://stackoverflow.com/questions/28785106/reactjs-why-is-passing-the-component-initial-state-a-prop-an-anti-pattern) 48 | -------------------------------------------------------------------------------- /anti-patterns/02.findDOMNode.md: -------------------------------------------------------------------------------- 1 | # 使用refs而不是findDOMNode()去获取DOM节点. 2 | 3 | 注意: 4 | React实际上也支持使用字符串作为ref, 来访问DOM节点. 但是需要注意的是这是一种已经不被官方推荐的用法. 5 | - [更多关于ref的知识](https://facebook.github.io/react/docs/more-about-refs.html) 6 | - [为什么字符串形式的ref已经不被推荐了?](http://stackoverflow.com/questions/37468913/why-ref-string-is-legacy) 7 | 8 | ##### 使用this找到DOM节点 9 | 10 | ###### 以前的做法: 11 | ```javascript 12 | class MyComponent extends Component { 13 | componentDidMount() { 14 | findDOMNode(this).scrollIntoView(); 15 | } 16 | 17 | render() { 18 | return
19 | } 20 | } 21 | ``` 22 | ###### 使用ref之后的做法 23 | ```javascript 24 | class MyComponent extends Component { 25 | componentDidMount() { 26 | this.node.scrollIntoView(); 27 | } 28 | 29 | render() { 30 | return
this.node = node}/> 31 | } 32 | } 33 | ``` 34 | ##### 使用字符串ref找到DOM节点 35 | ###### 使用字符串ref的做法 36 | ```javascript 37 | class MyComponent extends Component { 38 | componentDidMount() { 39 | findDOMNode(this.refs.something).scrollIntoView(); 40 | } 41 | 42 | render() { 43 | return ( 44 |
45 |
46 |
47 | ) 48 | } 49 | } 50 | ``` 51 | ###### 使用回调ref的做法 52 | ```javascript 53 | class MyComponent extends Component { 54 | componentDidMount() { 55 | this.something.scrollIntoView(); 56 | } 57 | 58 | render() { 59 | return ( 60 |
61 |
this.something = node}/> 62 |
63 | ) 64 | } 65 | } 66 | ``` 67 | ##### 调用子组件的ref 68 | ###### 不使用ref的做法: 69 | ```javascript 70 | class Field extends Component { 71 | render() { 72 | return 73 | } 74 | } 75 | 76 | class MyComponent extends Component { 77 | componentDidMount() { 78 | findDOMNode(this.refs.myInput).focus(); 79 | } 80 | 81 | render() { 82 | return ( 83 |
84 | Hello, 85 | 86 |
87 | ) 88 | } 89 | } 90 | ``` 91 | ###### 使用ref的做法 92 | ```javascript 93 | class Field extends Component { 94 | render() { 95 | return ( 96 | 97 | ) 98 | } 99 | } 100 | 101 | class MyComponent extends Component { 102 | componentDidMount() { 103 | this.inputNode.focus(); 104 | } 105 | 106 | render() { 107 | return ( 108 |
109 | Hello, 110 | this.inputNode = node}/> 111 |
112 | ) 113 | } 114 | } 115 | ``` 116 | 117 | ### 参考资料: 118 | - [ESLint Rule proposal: warn against using findDOMNode()](https://github.com/yannickcr/eslint-plugin-react/issues/678#issue-165177220) 119 | - [Refs and the DOM](https://facebook.github.io/react/docs/refs-and-the-dom.html) 120 | -------------------------------------------------------------------------------- /anti-patterns/03.mixins.md: -------------------------------------------------------------------------------- 1 | # 请使用高阶组件而不是Mixin 2 | 3 | #### 简单的例子 4 | ```javascript 5 | // 使用mixin 6 | var WithLink = React.createClass({ 7 | mixins: [React.addons.LinkedStateMixin], 8 | getInitialState: function () { 9 | return {message: 'Hello!'}; 10 | }, 11 | render: function () { 12 | return ; 13 | } 14 | }); 15 | 16 | // 使用高阶组件的做法 17 | var WithLink = React.createClass({ 18 | getInitialState: function () { 19 | return {message: 'Hello!'}; 20 | }, 21 | render: function () { 22 | return ; 23 | } 24 | }); 25 | ``` 26 | 27 | #### 更加详细的例子 28 | 29 | ```javascript 30 | // 使用Mixin Mixin 31 | var CarDataMixin = { 32 | componentDidMount: { 33 | // fetch car data and 34 | // call this.setState({carData: fetchedData}), 35 | // once data has been (asynchronously) fetched 36 | } 37 | }; 38 | 39 | var FirstView = React.createClass({ 40 | mixins: [CarDataMixin], 41 | render: function () { 42 | return ( 43 |
44 | 45 | 46 | 47 |
48 | ) 49 | } 50 | }); 51 | 52 | // 使用高阶组件 53 | var bindToCarData = function (Component) { 54 | return React.createClass({ 55 | componentDidMount: { 56 | // fetch car data and 57 | // call this.setState({carData: fetchedData}), 58 | // once data has been (asynchronously) fetched 59 | }, 60 | 61 | render: function () { 62 | return 63 | } 64 | }); 65 | }; 66 | 67 | // 将你的组件使用高阶组件包裹起来 68 | var FirstView = bindToCarData(React.createClass({ 69 | render: function () { 70 | return ( 71 |
72 | 73 | 74 | 75 |
76 | ) 77 | } 78 | })); 79 | ``` 80 | 81 | ### 参考资料: 82 | - [Mixins are dead - Long live higher ordercomponents](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) 83 | - [Mixins are considered harmful](https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html) 84 | - [Stackoverflow: Using mixins vs components for code reuse](http://stackoverflow.com/questions/21854938/using-mixins-vs-components-for-code-reuse-in-facebook-react) 85 | - [Stackoverflow: Composition instead of mixins in React](http://stackoverflow.com/questions/30845561/how-to-solve-this-using-composition-instead-of-mixins-in-react) 86 | -------------------------------------------------------------------------------- /anti-patterns/04.setState-in-componentWillMount.md: -------------------------------------------------------------------------------- 1 | # 避免在`componentWillMount()`中使用进行`setState`操作. 2 | 3 | `componentWillMount()` 在组件将要挂载时被立即调用. 这个调用发生在`render()`函数执行之前, 所以如果在`componentWillMount`里面设置了state, 这个设置的state是不会触发重新渲染的. 4 | 同样我们也需要注意不要在`componentWillMount()`中引入其他可能会导致问题的代码. 5 | 6 | 如果你有类似的需求, 请在`componentDidMount`里面完成. 7 | ```javascript 8 | function componentDidMount() { 9 | axios.get(`api/messages`) 10 | .then((result) => { 11 | const messages = result.data 12 | console.log("COMPONENT WILL Mount messages : ", messages); 13 | this.setState({ 14 | messages: [...messages.content] 15 | }) 16 | }) 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /anti-patterns/05.mutating-state.md: -------------------------------------------------------------------------------- 1 | # 不使用setState() 去操作state 2 | 导致的问题 3 | - 在state改变时组件不会重新渲染. 4 | - 在未来某个时候如果通过setState改变了state, 那么这次未通过setState去改变的state将会同样生效. 5 | 6 | #### 坏实践 7 | ``` javascript 8 | class SampleComponent extends Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | items: ['foo', 'bar'] 14 | }; 15 | 16 | this.handleClick = this.handleClick.bind(this); 17 | } 18 | 19 | handleClick() { 20 | // 坏实践: 我们手动更改了state而不是通过setState函数. 21 | this.state.items.push('lorem'); 22 | 23 | this.setState({ 24 | items: this.state.items 25 | }); 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | {this.state.items.length} 32 | 33 |
34 | ) 35 | } 36 | } 37 | ``` 38 | 39 | #### 好实践 40 | ``` javascript 41 | class SampleComponent extends Component { 42 | constructor(props) { 43 | super(props); 44 | 45 | this.state = { 46 | items: ['foo', 'bar'] 47 | }; 48 | 49 | this.handleClick = this.handleClick.bind(this); 50 | } 51 | 52 | handleClick() { 53 | // 我们使用了setState()方法来更新state, 组件将会在state更改后被更新. 54 | this.setState({ 55 | items: this.state.items.concat('lorem') 56 | }); 57 | } 58 | 59 | render() { 60 | return ( 61 |
62 | {this.state.items.length} 63 | 64 |
65 | ) 66 | } 67 | } 68 | ``` 69 | 70 | ### 参考资料: 71 | [React Design Patterns and best practices by Michele Bertoli.](https://github.com/MicheleBertoli/react-design-patterns-and-best-practices) -------------------------------------------------------------------------------- /anti-patterns/06.using-indexes-as-key.md: -------------------------------------------------------------------------------- 1 | # 使用简单索引作为key 2 | Keys 应该是稳定,可预测,并且唯一的. 这样React才能正确追踪到某一个元素. 3 | 4 | #### 坏实践 5 | 在下面这段代码中,每个元素的key事实上是它在todos这个数组里面的顺序, 而事实上更好的实践应该是把key和我们想要表达的数据紧紧关联在一起. 下面这种做法会阻碍React对于我们组件的优化. 6 | ```javascript 7 | {todos.map((todo, index) => 8 | 12 | )} 13 | ``` 14 | 15 | #### 好实践 16 | 假设`todo.id`是唯一的并且稳定的, React便能更好的去控制这些组件的更新(否则我们可能会面临大量重复创建的组件, 并且每次更新都是重新render这些组件.) 17 | ```javascript 18 | {todos.map((todo) => 19 | 21 | )} 22 | ``` 23 | 24 | ### 参考资料: 25 | - [React docs](https://facebook.github.io/react/docs/reconciliation.html#tradeoffs) 26 | - [Lin Clark's code cartoon](https://youtu.be/-t8eOoRsJ7M?t=981) 27 | -------------------------------------------------------------------------------- /anti-patterns/07.spreading-props-dom.md: -------------------------------------------------------------------------------- 1 | # 将props展平传到DOM上 2 | 当我们将展平(spread)的props传入子组件时我们便引入了风险, 因为我们可能往HTML标签上添加了它并不支持的属性. 3 | 4 | #### 坏实践 5 | 下面这个例子会在DOM元素上增加一个该元素本身并不支持的属性`flag`. 6 | ```javascript 7 | const Sample = () => (); 8 | const Spread = (props) => (
Test
); 9 | ``` 10 | #### 好实践 11 | 如果将HTML DOM元素需要接受的props分离出来再展开传入, 会是一种更安全的做法. 12 | ```javascript 13 | const Sample = () => (); 14 | const Spread = (props) => (
Test
); 15 | ``` 16 | 17 | 或者我们也可以使用`...rest`去过滤掉那些HTML DOM并不支持的属性. 18 | ```javascript 19 | const Sample = () => (); 20 | const Spread = ({ flag, ...domProps }) => (
Test
); 21 | ``` 22 | 23 | *需要注意的是* 24 | 25 | 在[这种情况](https://github.com/vasanthk/react-bits/issues/34)下, 当我们使用了[PureComponent](../perf-tips/02.pure-component.md)时, 即使`domProps`没有变化时, 组件还是会被重新渲染. 因为PureComponent对于对象使用的是[浅比较](https://facebook.github.io/react/docs/react-api.html#react.purecomponent) 26 | 27 | ### 参考资料: 28 | - [React Design Patterns and best practices by Michele Bertoli.](https://github.com/MicheleBertoli/react-design-patterns-and-best-practices) 29 | - [In React, children are just props: Kent C. Dodds' Tweet](https://twitter.com/kentcdodds/status/851406788549369856) 30 | -------------------------------------------------------------------------------- /anti-patterns/README.md: -------------------------------------------------------------------------------- 1 | # 坏实践 2 | 3 | 熟悉常见的坏实践能帮助我们理解React是如何工作的并且给我们重构代码提供不错的指导. -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">3.0.0", 3 | "plugins": [ 4 | "github", 5 | "-sharing", 6 | "sharing-plus" 7 | ], 8 | "pluginsConfig": { 9 | "github": { 10 | "url": "https://github.com/hateonion/react-bits-CN/" 11 | }, 12 | "sharing": { 13 | "douban": false, 14 | "facebook": true, 15 | "google": false, 16 | "hatenaBookmark": false, 17 | "instapaper": false, 18 | "line": false, 19 | "linkedin": true, 20 | "messenger": false, 21 | "pocket": true, 22 | "qq": true, 23 | "qzone": false, 24 | "stumbleupon": false, 25 | "twitter": true, 26 | "viber": false, 27 | "vk": false, 28 | "weibo": true, 29 | "whatsapp": false, 30 | "all": [ 31 | "facebook", 32 | "google", 33 | "twitter", 34 | "weibo", 35 | "instapaper", 36 | "linkedin", 37 | "pocket", 38 | "stumbleupon" 39 | ] 40 | } 41 | }, 42 | "title": "React-Bits 中文版", 43 | "links": { 44 | "sidebar": { 45 | "OY写字的地方": "https://hateonion.me" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /coding-style/README.md: -------------------------------------------------------------------------------- 1 | # Coding Style 2 | 3 | **Disclaimer:** 4 | 5 | This section is purely to familiarize oneself with different coding styles out there. It is not a set of coding standards or conventions! 6 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | > Your contributions are heartily ♡ welcome. (✿◠‿◠) 2 | -------------------------------------------------------------------------------- /docs/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public licenses. 379 | Notwithstanding, Creative Commons may elect to apply one of its public 380 | licenses to material it publishes and in those instances will be 381 | considered the "Licensor." Except for the limited purpose of indicating 382 | that material is shared under a Creative Commons public license or as 383 | otherwise permitted by the Creative Commons policies published at 384 | creativecommons.org/policies, Creative Commons does not authorize the 385 | use of the trademark "Creative Commons" or any other trademark or logo 386 | of Creative Commons without its prior written consent including, 387 | without limitation, in connection with any unauthorized modifications 388 | to any of its public licenses or any other arrangements, 389 | understandings, or agreements concerning use of licensed material. For 390 | the avoidance of doubt, this paragraph does not form part of the public 391 | licenses. 392 | 393 | Creative Commons may be contacted at creativecommons.org. 394 | -------------------------------------------------------------------------------- /docs/coding-style/README.md: -------------------------------------------------------------------------------- 1 | # Coding Style 2 | 3 | **Disclaimer:** 4 | 5 | This section is purely to familiarize oneself with different coding styles out there. It is not a set of coding standards or conventions! 6 | -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-edit-link/plugin.js: -------------------------------------------------------------------------------- 1 | require(["gitbook", "jQuery"], function(gitbook, $) { 2 | gitbook.events.bind('start', function (e, config) { 3 | var conf = config['edit-link']; 4 | var label = conf.label; 5 | var base = conf.base; 6 | var lang = gitbook.state.innerLanguage; 7 | if (lang) { 8 | // label can be a unique string for multi-languages site 9 | if (typeof label === 'object') label = label[lang]; 10 | 11 | lang = lang + '/'; 12 | } 13 | 14 | // Add slash at the end if not present 15 | if (base.slice(-1) != "/") { 16 | base = base + "/"; 17 | } 18 | 19 | gitbook.toolbar.createButton({ 20 | icon: 'fa fa-edit', 21 | text: label, 22 | onClick: function() { 23 | var filepath = gitbook.state.filepath; 24 | 25 | window.open(base + lang + filepath); 26 | } 27 | }); 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | // Configuration 3 | var MAX_SIZE = 4, 4 | MIN_SIZE = 0, 5 | BUTTON_ID; 6 | 7 | // Current fontsettings state 8 | var fontState; 9 | 10 | // Default themes 11 | var THEMES = [ 12 | { 13 | config: 'white', 14 | text: 'White', 15 | id: 0 16 | }, 17 | { 18 | config: 'sepia', 19 | text: 'Sepia', 20 | id: 1 21 | }, 22 | { 23 | config: 'night', 24 | text: 'Night', 25 | id: 2 26 | } 27 | ]; 28 | 29 | // Default font families 30 | var FAMILIES = [ 31 | { 32 | config: 'serif', 33 | text: 'Serif', 34 | id: 0 35 | }, 36 | { 37 | config: 'sans', 38 | text: 'Sans', 39 | id: 1 40 | } 41 | ]; 42 | 43 | // Return configured themes 44 | function getThemes() { 45 | return THEMES; 46 | } 47 | 48 | // Modify configured themes 49 | function setThemes(themes) { 50 | THEMES = themes; 51 | updateButtons(); 52 | } 53 | 54 | // Return configured font families 55 | function getFamilies() { 56 | return FAMILIES; 57 | } 58 | 59 | // Modify configured font families 60 | function setFamilies(families) { 61 | FAMILIES = families; 62 | updateButtons(); 63 | } 64 | 65 | // Save current font settings 66 | function saveFontSettings() { 67 | gitbook.storage.set('fontState', fontState); 68 | update(); 69 | } 70 | 71 | // Increase font size 72 | function enlargeFontSize(e) { 73 | e.preventDefault(); 74 | if (fontState.size >= MAX_SIZE) return; 75 | 76 | fontState.size++; 77 | saveFontSettings(); 78 | } 79 | 80 | // Decrease font size 81 | function reduceFontSize(e) { 82 | e.preventDefault(); 83 | if (fontState.size <= MIN_SIZE) return; 84 | 85 | fontState.size--; 86 | saveFontSettings(); 87 | } 88 | 89 | // Change font family 90 | function changeFontFamily(configName, e) { 91 | if (e && e instanceof Event) { 92 | e.preventDefault(); 93 | } 94 | 95 | var familyId = getFontFamilyId(configName); 96 | fontState.family = familyId; 97 | saveFontSettings(); 98 | } 99 | 100 | // Change type of color theme 101 | function changeColorTheme(configName, e) { 102 | if (e && e instanceof Event) { 103 | e.preventDefault(); 104 | } 105 | 106 | var $book = gitbook.state.$book; 107 | 108 | // Remove currently applied color theme 109 | if (fontState.theme !== 0) 110 | $book.removeClass('color-theme-'+fontState.theme); 111 | 112 | // Set new color theme 113 | var themeId = getThemeId(configName); 114 | fontState.theme = themeId; 115 | if (fontState.theme !== 0) 116 | $book.addClass('color-theme-'+fontState.theme); 117 | 118 | saveFontSettings(); 119 | } 120 | 121 | // Return the correct id for a font-family config key 122 | // Default to first font-family 123 | function getFontFamilyId(configName) { 124 | // Search for plugin configured font family 125 | var configFamily = $.grep(FAMILIES, function(family) { 126 | return family.config == configName; 127 | })[0]; 128 | // Fallback to default font family 129 | return (!!configFamily)? configFamily.id : 0; 130 | } 131 | 132 | // Return the correct id for a theme config key 133 | // Default to first theme 134 | function getThemeId(configName) { 135 | // Search for plugin configured theme 136 | var configTheme = $.grep(THEMES, function(theme) { 137 | return theme.config == configName; 138 | })[0]; 139 | // Fallback to default theme 140 | return (!!configTheme)? configTheme.id : 0; 141 | } 142 | 143 | function update() { 144 | var $book = gitbook.state.$book; 145 | 146 | $('.font-settings .font-family-list li').removeClass('active'); 147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); 148 | 149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); 150 | $book.addClass('font-size-'+fontState.size); 151 | $book.addClass('font-family-'+fontState.family); 152 | 153 | if(fontState.theme !== 0) { 154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); 155 | $book.addClass('color-theme-'+fontState.theme); 156 | } 157 | } 158 | 159 | function init(config) { 160 | // Search for plugin configured font family 161 | var configFamily = getFontFamilyId(config.family), 162 | configTheme = getThemeId(config.theme); 163 | 164 | // Instantiate font state object 165 | fontState = gitbook.storage.get('fontState', { 166 | size: config.size || 2, 167 | family: configFamily, 168 | theme: configTheme 169 | }); 170 | 171 | update(); 172 | } 173 | 174 | function updateButtons() { 175 | // Remove existing fontsettings buttons 176 | if (!!BUTTON_ID) { 177 | gitbook.toolbar.removeButton(BUTTON_ID); 178 | } 179 | 180 | // Create buttons in toolbar 181 | BUTTON_ID = gitbook.toolbar.createButton({ 182 | icon: 'fa fa-font', 183 | label: 'Font Settings', 184 | className: 'font-settings', 185 | dropdown: [ 186 | [ 187 | { 188 | text: 'A', 189 | className: 'font-reduce', 190 | onClick: reduceFontSize 191 | }, 192 | { 193 | text: 'A', 194 | className: 'font-enlarge', 195 | onClick: enlargeFontSize 196 | } 197 | ], 198 | $.map(FAMILIES, function(family) { 199 | family.onClick = function(e) { 200 | return changeFontFamily(family.config, e); 201 | }; 202 | 203 | return family; 204 | }), 205 | $.map(THEMES, function(theme) { 206 | theme.onClick = function(e) { 207 | return changeColorTheme(theme.config, e); 208 | }; 209 | 210 | return theme; 211 | }) 212 | ] 213 | }); 214 | } 215 | 216 | // Init configuration at start 217 | gitbook.events.bind('start', function(e, config) { 218 | var opts = config.fontsettings; 219 | 220 | // Generate buttons at start 221 | updateButtons(); 222 | 223 | // Init current settings 224 | init(opts); 225 | }); 226 | 227 | // Expose API 228 | gitbook.fontsettings = { 229 | enlargeFontSize: enlargeFontSize, 230 | reduceFontSize: reduceFontSize, 231 | setTheme: changeColorTheme, 232 | setFamily: changeFontFamily, 233 | getThemes: getThemes, 234 | setThemes: setThemes, 235 | getFamilies: getFamilies, 236 | setFamilies: setFamilies 237 | }; 238 | }); 239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-fontsettings/website.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Theme 1 3 | */ 4 | .color-theme-1 .dropdown-menu { 5 | background-color: #111111; 6 | border-color: #7e888b; 7 | } 8 | .color-theme-1 .dropdown-menu .dropdown-caret .caret-inner { 9 | border-bottom: 9px solid #111111; 10 | } 11 | .color-theme-1 .dropdown-menu .buttons { 12 | border-color: #7e888b; 13 | } 14 | .color-theme-1 .dropdown-menu .button { 15 | color: #afa790; 16 | } 17 | .color-theme-1 .dropdown-menu .button:hover { 18 | color: #73553c; 19 | } 20 | /* 21 | * Theme 2 22 | */ 23 | .color-theme-2 .dropdown-menu { 24 | background-color: #2d3143; 25 | border-color: #272a3a; 26 | } 27 | .color-theme-2 .dropdown-menu .dropdown-caret .caret-inner { 28 | border-bottom: 9px solid #2d3143; 29 | } 30 | .color-theme-2 .dropdown-menu .buttons { 31 | border-color: #272a3a; 32 | } 33 | .color-theme-2 .dropdown-menu .button { 34 | color: #62677f; 35 | } 36 | .color-theme-2 .dropdown-menu .button:hover { 37 | color: #f4f4f5; 38 | } 39 | .book .book-header .font-settings .font-enlarge { 40 | line-height: 30px; 41 | font-size: 1.4em; 42 | } 43 | .book .book-header .font-settings .font-reduce { 44 | line-height: 30px; 45 | font-size: 1em; 46 | } 47 | .book.color-theme-1 .book-body { 48 | color: #704214; 49 | background: #f3eacb; 50 | } 51 | .book.color-theme-1 .book-body .page-wrapper .page-inner section { 52 | background: #f3eacb; 53 | } 54 | .book.color-theme-2 .book-body { 55 | color: #bdcadb; 56 | background: #1c1f2b; 57 | } 58 | .book.color-theme-2 .book-body .page-wrapper .page-inner section { 59 | background: #1c1f2b; 60 | } 61 | .book.font-size-0 .book-body .page-inner section { 62 | font-size: 1.2rem; 63 | } 64 | .book.font-size-1 .book-body .page-inner section { 65 | font-size: 1.4rem; 66 | } 67 | .book.font-size-2 .book-body .page-inner section { 68 | font-size: 1.6rem; 69 | } 70 | .book.font-size-3 .book-body .page-inner section { 71 | font-size: 2.2rem; 72 | } 73 | .book.font-size-4 .book-body .page-inner section { 74 | font-size: 4rem; 75 | } 76 | .book.font-family-0 { 77 | font-family: Georgia, serif; 78 | } 79 | .book.font-family-1 { 80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 81 | } 82 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal { 83 | color: #704214; 84 | } 85 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a { 86 | color: inherit; 87 | } 88 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, 89 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2, 90 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3, 91 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4, 92 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5, 93 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { 94 | color: inherit; 95 | } 96 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, 97 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 { 98 | border-color: inherit; 99 | } 100 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { 101 | color: inherit; 102 | } 103 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr { 104 | background-color: inherit; 105 | } 106 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote { 107 | border-color: inherit; 108 | } 109 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, 110 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { 111 | background: #fdf6e3; 112 | color: #657b83; 113 | border-color: #f8df9c; 114 | } 115 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight { 116 | background-color: inherit; 117 | } 118 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th, 119 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td { 120 | border-color: #f5d06c; 121 | } 122 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr { 123 | color: inherit; 124 | background-color: #fdf6e3; 125 | border-color: #444444; 126 | } 127 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { 128 | background-color: #fbeecb; 129 | } 130 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal { 131 | color: #bdcadb; 132 | } 133 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a { 134 | color: #3eb1d0; 135 | } 136 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, 137 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2, 138 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3, 139 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4, 140 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5, 141 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { 142 | color: #fffffa; 143 | } 144 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, 145 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 { 146 | border-color: #373b4e; 147 | } 148 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { 149 | color: #373b4e; 150 | } 151 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr { 152 | background-color: #373b4e; 153 | } 154 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote { 155 | border-color: #373b4e; 156 | } 157 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, 158 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { 159 | color: #9dbed8; 160 | background: #2d3143; 161 | border-color: #2d3143; 162 | } 163 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight { 164 | background-color: #282a39; 165 | } 166 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th, 167 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td { 168 | border-color: #3b3f54; 169 | } 170 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr { 171 | color: #b6c2d2; 172 | background-color: #2d3143; 173 | border-color: #3b3f54; 174 | } 175 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { 176 | background-color: #35394b; 177 | } 178 | .book.color-theme-1 .book-header { 179 | color: #afa790; 180 | background: transparent; 181 | } 182 | .book.color-theme-1 .book-header .btn { 183 | color: #afa790; 184 | } 185 | .book.color-theme-1 .book-header .btn:hover { 186 | color: #73553c; 187 | background: none; 188 | } 189 | .book.color-theme-1 .book-header h1 { 190 | color: #704214; 191 | } 192 | .book.color-theme-2 .book-header { 193 | color: #7e888b; 194 | background: transparent; 195 | } 196 | .book.color-theme-2 .book-header .btn { 197 | color: #3b3f54; 198 | } 199 | .book.color-theme-2 .book-header .btn:hover { 200 | color: #fffff5; 201 | background: none; 202 | } 203 | .book.color-theme-2 .book-header h1 { 204 | color: #bdcadb; 205 | } 206 | .book.color-theme-1 .book-body .navigation { 207 | color: #afa790; 208 | } 209 | .book.color-theme-1 .book-body .navigation:hover { 210 | color: #73553c; 211 | } 212 | .book.color-theme-2 .book-body .navigation { 213 | color: #383f52; 214 | } 215 | .book.color-theme-2 .book-body .navigation:hover { 216 | color: #fffff5; 217 | } 218 | /* 219 | * Theme 1 220 | */ 221 | .book.color-theme-1 .book-summary { 222 | color: #afa790; 223 | background: #111111; 224 | border-right: 1px solid rgba(0, 0, 0, 0.07); 225 | } 226 | .book.color-theme-1 .book-summary .book-search { 227 | background: transparent; 228 | } 229 | .book.color-theme-1 .book-summary .book-search input, 230 | .book.color-theme-1 .book-summary .book-search input:focus { 231 | border: 1px solid transparent; 232 | } 233 | .book.color-theme-1 .book-summary ul.summary li.divider { 234 | background: #7e888b; 235 | box-shadow: none; 236 | } 237 | .book.color-theme-1 .book-summary ul.summary li i.fa-check { 238 | color: #33cc33; 239 | } 240 | .book.color-theme-1 .book-summary ul.summary li.done > a { 241 | color: #877f6a; 242 | } 243 | .book.color-theme-1 .book-summary ul.summary li a, 244 | .book.color-theme-1 .book-summary ul.summary li span { 245 | color: #877f6a; 246 | background: transparent; 247 | font-weight: normal; 248 | } 249 | .book.color-theme-1 .book-summary ul.summary li.active > a, 250 | .book.color-theme-1 .book-summary ul.summary li a:hover { 251 | color: #704214; 252 | background: transparent; 253 | font-weight: normal; 254 | } 255 | /* 256 | * Theme 2 257 | */ 258 | .book.color-theme-2 .book-summary { 259 | color: #bcc1d2; 260 | background: #2d3143; 261 | border-right: none; 262 | } 263 | .book.color-theme-2 .book-summary .book-search { 264 | background: transparent; 265 | } 266 | .book.color-theme-2 .book-summary .book-search input, 267 | .book.color-theme-2 .book-summary .book-search input:focus { 268 | border: 1px solid transparent; 269 | } 270 | .book.color-theme-2 .book-summary ul.summary li.divider { 271 | background: #272a3a; 272 | box-shadow: none; 273 | } 274 | .book.color-theme-2 .book-summary ul.summary li i.fa-check { 275 | color: #33cc33; 276 | } 277 | .book.color-theme-2 .book-summary ul.summary li.done > a { 278 | color: #62687f; 279 | } 280 | .book.color-theme-2 .book-summary ul.summary li a, 281 | .book.color-theme-2 .book-summary ul.summary li span { 282 | color: #c1c6d7; 283 | background: transparent; 284 | font-weight: 600; 285 | } 286 | .book.color-theme-2 .book-summary ul.summary li.active > a, 287 | .book.color-theme-2 .book-summary ul.summary li a:hover { 288 | color: #f4f4f5; 289 | background: #252737; 290 | font-weight: 600; 291 | } 292 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-lunr/lunr.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.12 3 | * Copyright (C) 2015 Oliver Nightingale 4 | * MIT Licensed 5 | * @license 6 | */ 7 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.5.12",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(t){return arguments.length&&null!=t&&void 0!=t?Array.isArray(t)?t.map(function(t){return t.toLowerCase()}):t.toString().trim().toLowerCase().split(/[\s\-]+/):[]},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,o=0;n>o;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;no;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $('
  • ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $('

    '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('

    ').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /docs/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /docs/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateonion/react-bits-CN/72aea1e7d280612341ba101bf82c0173816ca180/docs/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bits", 3 | "version": "1.0.0", 4 | "repository": "https://github.com/vasanthk/react-bits.git", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "docs": "gitbook serve" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gotchas/01.pure-render-checks.md: -------------------------------------------------------------------------------- 1 | # 保证渲染的性能 2 | 为了保障组件的性能, 我们有的时候会从组件渲染的角度出发. 3 | 4 | 更干净的render函数? 这个概念可能会有点让人疑惑. 5 | 6 | 其实在这里干净是指我们在`shouldComponentUpdate`这个生命周期函数里面去做浅比较, 从而避免不必要的渲染. 7 | 8 | 关于上面的干净渲染, 现有的一些实现包括React.PureComponent, PureRenderMixin, recompose/pure 等等. 9 | 10 | #### 第一个例子 11 | 12 | ##### 坏实践 13 | ```javascript 14 | class Table extends PureComponent { 15 | render() { 16 | return ( 17 |

    18 | {this.props.items.map(i => 19 | 20 | )} 21 |
    22 | ); 23 | } 24 | } 25 | ``` 26 | 27 | **这种写法的问题在于`{this.props.options || []}`- 这种写法会导致所有的Cell都被重新渲染即使只有一个cell发生了改变. 为什么会发生这种事呢?** 28 | 29 | 仔细观察你会发现, options这个数组被传到了Cell这个组件上, 一般情况下, 这不会导致什么问题. 因为如果有其他的Cell组件, 组件会在有props发生改变的时候浅对比props并且跳过渲染(因为对于其他Cell组件, props并没有发生改变). 但是在这个例子里面, 当options为null时, 一个默认的空数组就会被当成Props传到组件里面去. 事实上每次传入的`[]`都相当于创建了新的Array实例. 在JavaScript里面, 不同的实例是有不同的实体的, 所以浅比较在这种情况下总是会返回false, 然后组件就会被重新渲染. 因为两个实体不是同一个实体. 这就完全破坏了React对于我们组件渲染的优化. 30 | 31 | ##### 好实践 32 | ```javascript 33 | const defaultval = []; // <--- 也可以使用defaultProps 34 | class Table extends PureComponent { 35 | render() { 36 | return ( 37 |
    38 | {this.props.items.map(i => 39 | 40 | )} 41 |
    42 | ); 43 | } 44 | } 45 | ``` 46 | #### 第二个例子 47 | 在render函数里面调用函数也可能导致和上面相同的问题. 48 | 49 | ##### 坏实践 50 | ```javascript 51 | class App extends PureComponent { 52 | render() { 53 | return this.props.update(e.target.value)}/>; 55 | } 56 | } 57 | ``` 58 | ##### 又一个坏实践 59 | ```javascript 60 | class App extends PureComponent { 61 | update(e) { 62 | this.props.update(e.target.value); 63 | } 64 | 65 | render() { 66 | return ; 67 | } 68 | } 69 | ``` 70 | 在上面的两个坏实践中, 每次我们都会去创建一个新的函数实体. 和第一个例子类似, 新的函数实体会让我们的浅比较返回false, 导致组件被重新渲染. 71 | 所以我们需要在更早的时候去bind我们的函数. 72 | 73 | ##### 好实践 74 | ```javascript 75 | class App extends PureComponent { 76 | constructor(props) { 77 | super(props); 78 | this.update = this.update.bind(this); 79 | } 80 | 81 | update(e) { 82 | this.props.update(e.target.value); 83 | } 84 | 85 | render() { 86 | return ; 87 | } 88 | } 89 | ``` 90 | ##### 坏实践 91 | ```javascript 92 | class Component extends React.Component { 93 | state = {clicked: false}; 94 | 95 | onClick() { 96 | this.setState({clicked: true}) 97 | } 98 | 99 | render() { 100 | // 如果options为空的话, 每次都会创建一个新的{test:1}对象 101 | const options = this.props.options || {test: 1}; 102 | 103 | return this.onClick(event) 109 | /> 110 | } 111 | } 112 | ``` 113 | ##### 好实践 114 | ```javascript 115 | class Component extends React.Component { 116 | state = {clicked: false}; 117 | options = {test: 1}; 118 | 119 | onClick = () => { 120 | this.setState({clicked: true}) 121 | }; 122 | 123 | render() { 124 | // Options这个对象只被创建了一次. 125 | const options = this.props.options || this.options; 126 | 127 | return 132 | } 133 | } 134 | ``` 135 | 136 | ### 参考资料: 137 | - https://medium.com/@esamatti/react-js-pure-render-performance-anti-pattern-fb88c101332f 138 | - https://github.com/nfour/js-structures/blob/master/guides/react-anti-patterns.md#pure-render-immutability 139 | - [Optimizing React Rendering](https://flexport.engineering/optimizing-react-rendering-part-1-9634469dca02) 140 | -------------------------------------------------------------------------------- /gotchas/02.synthetic-events.md: -------------------------------------------------------------------------------- 1 | # React中的 Synthetic 事件 2 | React在处理事件(event时), 事实上使用了SyntheticEvent对象包裹了原生的event对象. 3 | 4 | 这些React自己维护的对象是相互联系的, 意味着如果对于某一个事件, 我们给出了对应的响应函数(handler), 其他的SyntheticEvent对象也是可以重用的.这也是React提升性能的秘诀之一. 5 | 但是这也意味着, 如果想要通过异步的方式访问事件对象是不可能的, 因为出于reuse的原因, 事件对象里面的值都被重置了. 6 | 7 | 下面这段代码会在控制台里面打出null, 因为事件在SyntheticEvent池中被重用了. 8 | ```javascript 9 | function handleClick(event) { 10 | setTimeout(function () { 11 | console.log(event.target.name); 12 | }, 1000); 13 | } 14 | ``` 15 | 为了避免这种情况, 你需要去保存你关心的事件的属性. 16 | ```javascript 17 | function handleClick(event) { 18 | let name = event.target.name; 19 | setTimeout(function () { 20 | console.log(name); 21 | }, 1000); 22 | } 23 | ``` 24 | 25 | ### 参考资料: 26 | - [React/Redux: Best practices & gotchas](https://medium.com/nick-parsons/react-redux-best-practices-gotchas-56cf61c1c415) 27 | - [React events in depth w/ Kent C. Dodds, Ben Alpert, & Dan Abramov](https://www.youtube.com/watch?v=dRo_egw7tBc) -------------------------------------------------------------------------------- /gotchas/README.md: -------------------------------------------------------------------------------- 1 | # React中的陷阱 2 | 3 | 在绝大多数情况下, React都是清晰直观的. 但是也不乏有一些小陷阱, 不注意的话有时候也会给你"意外的惊喜". 下面我们就来介绍一下这些小陷阱 4 | 5 | ## 参考资料 6 | 7 | [React Gotchas](https://daveceddia.com/react-gotchas/) 8 | 9 | [Top 5 React Gotchas](http://joelgriffith.net/top-5-react-gotchas/) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bits", 3 | "version": "1.0.0", 4 | "repository": "https://github.com/vasanthk/react-bits.git", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "docs": "gitbook serve" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /patterns/18.conditionals-in-jsx.md: -------------------------------------------------------------------------------- 1 | # JSX中的状态分支 2 | 3 | 在以下情况下,和使用三元运算符相比 4 | ```javascript 5 | const sampleComponent = () => { 6 | return isTrue ?

    True!

    : 7 | }; 8 | ``` 9 | 10 | 使用&&符号的表达式简写会是一种更好的实践 11 | ```javascript 12 | const sampleComponent = () => { 13 | return isTrue &&

    True!

    14 | }; 15 | ``` 16 | 如果像下面一样有很多的三元运算符的话: 17 | 18 | ```javascript 19 | // Y soo many ternary??? :-/ 20 | const sampleComponent = () => { 21 | return ( 22 |
    23 | {flag && flag2 && !flag3 24 | ? flag4 25 | ?

    Blah

    26 | : flag5 27 | ?

    Meh

    28 | :

    Herp

    29 | :

    Derp

    30 | } 31 |
    32 | ) 33 | }; 34 | ``` 35 | 36 | - 最佳实践: 将逻辑移到子组件内部 37 | - 另一种hack的做法: 使用IIFE(Immediately-Invoked Function Expression 立即执行函数) 38 | 39 | 有一些库可以解决用jsx控制组件状态的问题,但是这些外部依赖并不是必须的,我们可以使用 40 | [IIFE](http://stackoverflow.com/questions/8228281/what-is-the-function-construct-in-javascript) 将if-else的逻辑封装到组件内部,外部调用者并不需要关心这些逻辑,正常调用即可。 41 | 42 | ```javascript 43 | const sampleComponent = () => { 44 | return ( 45 |
    46 | { 47 | (() => { 48 | if (flag && flag2 && !flag3) { 49 | if (flag4) { 50 | return

    Blah

    51 | } else if (flag5) { 52 | return

    Meh

    53 | } else { 54 | return

    Herp

    55 | } 56 | } else { 57 | return

    Derp

    58 | } 59 | })() 60 | } 61 |
    62 | ) 63 | }; 64 | ``` 65 | 66 | 或者也可以在满足条件的时候使用return强制跳出函数,这样能避免后面冗余的判断执行。 67 | 68 | ```javascript 69 | const sampleComponent = () => { 70 | const basicCondition = flag && flag2 && !flag3; 71 | if (!basicCondition) return

    Derp

    ; 72 | if (flag4) return

    Blah

    ; 73 | if (flag5) return

    Meh

    ; 74 | return

    Herp

    75 | } 76 | ``` 77 | 78 | ### 参考资料: 79 | - https://engineering.musefind.com/our-best-practices-for-writing-react-components-dec3eb5c3fc8 80 | - [Conditional rendering](https://facebook.github.io/react/docs/conditional-rendering.html) 81 | 82 | 83 | -------------------------------------------------------------------------------- /patterns/19.async-nature-of-setState.md: -------------------------------------------------------------------------------- 1 | # setState函数的异步性 2 | 3 | ## 简述 4 | 5 | 在某些情况下,React框架出于性能优化考虑,可能会将多次state更新合并成一次更新。正因为如此,setState实际上是一个异步的函数。 6 | 但是,有一些行为也会阻止React框架本身对于多次state更新的合并,从而让state的更新变得同步化。 7 | 比如: eventListeners, Ajax, setTimeout 等等。 8 | 9 | ### 详解 10 | 11 | 当setState() 函数执行的时候,函数会创建一个暂态的state作为过渡state,而不是立即修改this.state。 12 | 如果在调用setState()函数之后尝试去访问this.state,你得到的可能还是setState()函数执行之前的结果。 13 | 在使用setState()的情况下,看起来同步执行的代码其实执行顺序是得不到保证的。原因上面也提到过,React可能会将多次state更新合并成一次更新来优化性能。 14 | 15 | 运行下面这段代码,你会发现当和addEventListener, setTimeout 函数或者发出AJAX call的时候,调用setState, state会发生改变。并且render函数会在setState()函数被触发之后马上被调用。那么到底发生了什么呢?事实上,类似setTimeout()函数或者发出ajax call的fetch函数属于调用浏览器层面的API,这些函数的执行并不存在与React的上下文中,所以React并不能够像控制其他存在与其上下文中的函数一样,将多次state更新合并成一次。 16 | 17 | 在上面这些例子中,React框架之所以在选择在调用setState函数之后立即更新state而不是采用框架默认的方式,即合并多次state更新为一次更新,是因为这些函数调用(fetch,setTimeout等浏览器层面的API调用)并不处于React框架的上下文中,React没有办法对其进行控制。React在此时采用的策略就是及时更新,确保在这些函数执行之后的其他代码能拿到正确的数据(即更新过的state)。 18 | 19 | ```javascript 20 | class TestComponent extends React.Component { 21 | 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | dollars: 10 26 | } 27 | } 28 | 29 | componentDidMount() { 30 | // Add custom event via `addEventListener` 31 | // 32 | // The list of supported React events does include `mouseleave` 33 | // via `onMouseLeave` prop 34 | // 35 | // However, we are not adding the event the `React way` - this will have 36 | // effects on how state mutates 37 | // 38 | // Check the list here - https://facebook.github.io/react/docs/events.html 39 | document.getElementById('testButton').addEventListener('mouseleave', this.onMouseLeaveHandler); 40 | 41 | // Add JS timeout 42 | // 43 | // Again,outside React `world` - this will also have effects on how state 44 | // mutates 45 | setTimeout(this.onTimeoutHandler, 10000); 46 | 47 | // Make AJAX request 48 | fetch('https://api.github.com/users') 49 | .then(this.onAjaxCallback); 50 | } 51 | 52 | onClickHandler = () => { 53 | console.log('State before (_onClickHandler): ' + JSON.stringify(this.state)); 54 | this.setState({ 55 | dollars: this.state.dollars + 10 56 | }); 57 | console.log('State after (_onClickHandler): ' + JSON.stringify(this.state)); 58 | } 59 | 60 | onMouseLeaveHandler = () => { 61 | console.log('State before (mouseleave): ' + JSON.stringify(this.state)); 62 | this.setState({ 63 | dollars: this.state.dollars + 20 64 | }); 65 | console.log('State after (mouseleave): ' + JSON.stringify(this.state)); 66 | } 67 | 68 | onTimeoutHandler = () => { 69 | console.log('State before (timeout): ' + JSON.stringify(this.state)); 70 | this.setState({ 71 | dollars: this.state.dollars + 30 72 | }); 73 | console.log('State after (timeout): ' + JSON.stringify(this.state)); 74 | } 75 | 76 | onAjaxCallback = (err, res) => { 77 | if (err) { 78 | console.log('Error in AJAX call: ' + JSON.stringify(err)); 79 | return; 80 | } 81 | 82 | console.log('State before (AJAX call): ' + JSON.stringify(this.state)); 83 | this.setState({ 84 | dollars: this.state.dollars + 40 85 | }); 86 | console.log('State after (AJAX call): ' + JSON.stringify(this.state)); 87 | } 88 | 89 | render() { 90 | console.log('State in render: ' + JSON.stringify(this.state)); 91 | 92 | return ( 93 | 98 | ); 99 | } 100 | } 101 | 102 | ReactDOM.render( 103 | , 104 | document.getElementById('app') 105 | ); 106 | ``` 107 | 108 | ### 解决setState函数异步的办法? 109 | 110 | 根据React官方文档,setState函数实际上接收两个参数,其中第二个参数类型是一个函数,作为setState函数执行后的回调。通过传入回调函数的方式,React可以保证传入的回调函数一定是在setState成功更新this.state之后再执行。 111 | 112 | #### 例子 113 | 114 | ```javascript 115 | _onClickHandler: function _onClickHandler() { 116 | console.log('State before (_onClickHandler): ' + JSON.stringify(this.state)); 117 | this.setState({ 118 | dollars: this.state.dollars + 10 119 | }, () => { 120 | console.log('Here state will always be updated to latest version!'); 121 | console.log('State after (_onClickHandler): ' + JSON.stringify(this.state)); 122 | }); 123 | } 124 | ``` 125 | 126 | #### 更多关于setState的小知识 127 | 128 | 其实setState作为一个函数,本身是同步的。只是因为在setState的内部实现中,使用了React updater的enqueueState 或者 enqueueCallback方法,才造成了异步。 129 | 130 | 下面这段是React源码中setState的实现: 131 | 132 | ```javascript 133 | ReactComponent.prototype.setState = function(partialState, callback) { 134 | invariant( 135 | typeof partialState === 'object' || 136 | typeof partialState === 'function' || 137 | partialState == null, 138 | 'setState(...): takes an object of state variables to update or a ' + 139 | 'function which returns an object of state variables.' 140 | ); 141 | this.updater.enqueueSetState(this, partialState); 142 | if (callback) { 143 | this.updater.enqueueCallback(this, callback, 'setState'); 144 | } 145 | }; 146 | ``` 147 | 148 | 而updater的这两个方法,又和React底层的Virtual Dom(虚拟DOM树)的diff算法有紧密的关系,所以真正决定同步还是异步的其实是Virtual DOM的diff算法。 149 | 150 | ## 参考资料: 151 | - https://medium.com/@wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3#.jhdhncws3 152 | - https://www.bennadel.com/blog/2893-setstate-state-mutation-operation-may-be-synchronous-in-reactjs.htm 153 | -------------------------------------------------------------------------------- /patterns/20.dependency-injection.md: -------------------------------------------------------------------------------- 1 | # 依赖注入 2 | 3 | 在React中,想做[依赖注入(Dependency Injection)](https://www.youtube.com/watch?v=IKD2-MAkXyQ)其实相当简单。请看下面这个例子: 4 | 5 | ```javascript 6 | // Title.jsx 7 | export default function Title(props) { 8 | return

    { props.title }

    ; 9 | } 10 | ``` 11 | 12 | ```javascript 13 | // Header.jsx 14 | import Title from './Title.jsx'; 15 | export default function Header() { 16 | return ( 17 |
    18 | 19 | </header> 20 | ); 21 | } 22 | ``` 23 | ```javascript 24 | // App.jsx 25 | import Header from './Header.jsx'; 26 | class App extends React.Component { 27 | constructor(props) { 28 | super(props); 29 | this.state = { title: 'React Dependency Injection' }; 30 | } 31 | render() { 32 | return <Header />; 33 | } 34 | } 35 | ``` 36 | 37 | 试想,我们想把"React Dependency Injection"这个字符串传到我们的Title组件里面去。比较直观的方法是把这个字符串作为props传入Header组件,然后Header组件将这个字符串作为props传入Title组件。 38 | 在只有三个组件相互嵌套的情况下,上面的这种解决方式似乎能满足我们的需要。但是设想一下,随着组件数量的增加以及组件嵌套层次的加深,许多的组件都需要接收该组件本身并不需要关心的props,然后且简单地传递给子组件。直观上来看,上面的这种方法在这种情况下,似乎就不太适用了。 39 | 40 | 不过不用担心,事实上我们还有很多方法能实现依赖的注入,其中之一就是高阶组件(high-order component)。 41 | 42 | ```javascript 43 | // inject.jsx 44 | var title = 'React Dependency Injection'; 45 | export default function inject(Component) { 46 | return class Injector extends React.Component { 47 | render() { 48 | return ( 49 | <Component 50 | {...this.state} 51 | {...this.props} 52 | title={ title } 53 | /> 54 | ) 55 | } 56 | }; 57 | } 58 | ``` 59 | ```javascript 60 | // Title.jsx 61 | export default function Title(props) { 62 | return <h1>{ props.title }</h1>; 63 | } 64 | ``` 65 | ```javascript 66 | // Header.jsx 67 | import inject from './inject.jsx'; 68 | import Title from './Title.jsx'; 69 | 70 | var EnhancedTitle = inject(Title); 71 | export default function Header() { 72 | return ( 73 | <header> 74 | <EnhancedTitle /> 75 | </header> 76 | ); 77 | } 78 | ``` 79 | 80 | 在上面这种实现中,title这个字符串作为我们的数据没有被注入到整个的组件树中。相反,我们的数据只传递给了需要关注这个数据的组件。 81 | 这种实现看上去比上面优雅了很多,但事实上并没有完全解决我们的问题。 82 | 我们将title这个字符串通过inject这个高阶组件注入了进去,但是如果我的title字符串只能在我的Header组件这个层级获得呢?那么是不是意味着我还需要再从Header组件传递给inject组件呢? 83 | 84 | 这是一个实际可能会发生的问题,为了解决这个问题,接下来需要介绍一下React的Context API。 85 | 在React框架中,存在着context这样一个概念。context有点类似与事件总线(Event Bus),是一个无论在哪都可以访问的对象。不同的是,但是在context里面维持的全部都是我们的数据而非事件。context贯穿于整个组件树中,所有的组件都可以访问context。 86 | 87 | 使用方法 88 | ```javascript 89 | var context = { title: 'React in patterns' }; 90 | class App extends React.Component { 91 | getChildContext() { 92 | return context; 93 | } 94 | // ... 95 | } 96 | 97 | App.childContextTypes = { 98 | title: PropTypes.string 99 | }; 100 | ``` 101 | A place where we need data 102 | ```javascript 103 | class Inject extends React.Component { 104 | render() { 105 | var title = this.context.title; 106 | // ... 107 | } 108 | } 109 | Inject.contextTypes = { 110 | title: PropTypes.string 111 | }; 112 | ``` 113 | 114 | 需要注意的是,在最新的React官方文档中,Context已经不太被官方推荐使用了。[Why Not To Use Context](https://reactjs.org/docs/context.html#why-not-to-use-context) 115 | 116 | ### 参考资料: 117 | - [What is Dependency Injection?](https://www.youtube.com/watch?v=IKD2-MAkXyQ) 118 | - [The Basics of Dependency Injection](https://www.youtube.com/watch?v=jXhdOTw1q5Q) 119 | - [Dependency injection in JavaScript](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) 120 | - [DI In React](https://github.com/krasimir/react-in-patterns/tree/master/patterns/dependency-injection) 121 | -------------------------------------------------------------------------------- /patterns/21.context-wrapper.md: -------------------------------------------------------------------------------- 1 | # 包裹Context 2 | 相比于单纯的数据对象,将context包装成一个提供一些方法的对象会是更好的实践。因为这样能提供一些方法供我们操作context里面的数据。 3 | 4 | ```javascript 5 | // dependencies.js 6 | export default { 7 | data: {}, 8 | get(key) { 9 | return this.data[key]; 10 | }, 11 | register(key, value) { 12 | this.data[key] = value; 13 | } 14 | } 15 | 16 | ``` 17 | 经过了包装的Context,可以通过类似于下面的这种方法使用。 18 | ```javascript 19 | import dependencies from './dependencies'; 20 | dependencies.register('title', 'React in patterns'); 21 | 22 | class App extends React.Component { 23 | getChildContext() { 24 | return dependencies; 25 | } 26 | render() { 27 | return <Header />; 28 | } 29 | } 30 | 31 | // 我们还可以对context中的数据做类型校验 32 | App.childContextTypes = { 33 | data: PropTypes.object, 34 | get: PropTypes.func, 35 | register: PropTypes.func 36 | }; 37 | ``` 38 | 39 | 这样我们的Title组件就能直接从Context中获取数据了。 40 | ```javascript 41 | // Title.jsx 42 | export default class Title extends React.Component { 43 | render() { 44 | return <h1>{ this.context.get('title') }</h1> 45 | } 46 | } 47 | Title.contextTypes = { 48 | data: PropTypes.object, 49 | get: PropTypes.func, 50 | register: PropTypes.func 51 | }; 52 | ``` 53 | 54 | 一般来说,我们不需要每次在使用context的地方都对context内的数据做类型校验。这种功能完全可以借由一个高阶组件派生出来。我们甚至可以使用高阶组件来作为我们操作context的代理,来替代我们对于context的直接操作。 55 | 56 | 比如: 我们可以使用一个高阶组件来替代我们直接对于this.context.get('title')方法的调用。 57 | ```javascript 58 | // Title.jsx 59 | import wire from './wire'; 60 | 61 | function Title(props) { 62 | return <h1>{ props.title }</h1>; 63 | } 64 | 65 | export default wire(Title, ['title'], function resolve(title) { 66 | return { title }; 67 | }); 68 | ``` 69 | 70 | wire这个函数的接收一个React Element作为第一个参数。第二个参数为一个数组,数组内容为组件所依赖的数据在context中的key。第三个参数我把它叫做mapper,mapper这个函数会将context里面的原始数据进行处理,然后以对象的形式返回组件所需要的数据。在我们的Title组件的例子中,我们在只需要在第二个参数中传入title作为我们依赖的描述,mapper就会返回在context中的title。 71 | 但是在实际应用中,我们所需要的数据可能会很多,依赖的描述形式也可能千变万化,可能是一堆数据的集合,也可能是读自某个配置文件。但是我们都可以通过将依赖的描述传入wire函数,让wire函数去帮我们将所有必要的依赖传入我们的组件,而不是传入所有的context。 72 | 73 | 下面是一种可能的实现: 74 | ```javascript 75 | export default function wire(Component, dependencies, mapper) { 76 | class Inject extends React.Component { 77 | render() { 78 | var resolved = dependencies.map(this.context.get.bind(this.context)); 79 | var props = mapper(...resolved); 80 | 81 | return React.createElement(Component, props); 82 | } 83 | } 84 | Inject.contextTypes = { 85 | data: PropTypes.object, 86 | get: PropTypes.func, 87 | register: PropTypes.func 88 | }; 89 | return Inject; 90 | }; 91 | ``` 92 | 93 | Inject 是一个能访问context并且获取所有在dependency数组中列出的数据的高阶组件。mapper是一个能接受context数据作为输入,将所有需要的数据从context中取出转换成组件props的函数。 94 | 95 | #### 不依赖context的另外一种实现 96 | 我们使用一个单例来注册/获取所有的依赖 97 | ```javascript 98 | var dependencies = {}; 99 | 100 | export function register(key, dependency) { 101 | dependencies[key] = dependency; 102 | } 103 | 104 | export function fetch(key) { 105 | if (key in dependencies) return dependencies[key]; 106 | throw new Error(`"${ key } is not registered as dependency.`); 107 | } 108 | 109 | export function wire(Component, deps, mapper) { 110 | return class Injector extends React.Component { 111 | constructor(props) { 112 | super(props); 113 | this._resolvedDependencies = mapper(...deps.map(fetch)); 114 | } 115 | render() { 116 | return ( 117 | <Component 118 | {...this.state} 119 | {...this.props} 120 | {...this._resolvedDependencies} 121 | /> 122 | ); 123 | } 124 | }; 125 | } 126 | ``` 127 | 128 | 我们把dependencies这个对象存放在全局范围(不是应用全局范围,而是包全局范围)。同时我们export出register和fetch两个函数用于读写我们的dependencies对象。(有点类似javascript class里面getter和setter的实现)。至此,wire函数的实现就已经完成了。wire函数接受一个React组件作为输入,输出一个高阶组件。 129 | 130 | 我们在这个返回的高阶组件中去处理我们的依赖并将其转化为props,在render函数中传入子组件(即我们传入想真正渲染的组件) 131 | 132 | 遵循上面这个模式,我们实现了以下代码。di.jsx这个helper帮助我们将我们应用的所有依赖注册好,并且通过这个helper我们可以在整个应用的域里面随时取得我们想需要的依赖。 133 | 134 | ```javascript 135 | // app.jsx 136 | import Header from './Header.jsx'; 137 | import { register } from './di.jsx'; 138 | 139 | register('my-awesome-title', 'React in patterns'); 140 | 141 | class App extends React.Component { 142 | render() { 143 | return <Header />; 144 | } 145 | } 146 | ``` 147 | ```javascript 148 | // Header.jsx 149 | import Title from './Title.jsx'; 150 | 151 | export default function Header() { 152 | return ( 153 | <header> 154 | <Title /> 155 | </header> 156 | ); 157 | } 158 | ``` 159 | ```javascript 160 | // Title.jsx 161 | import { wire } from './di.jsx'; 162 | 163 | var Title = function(props) { 164 | return <h1>{ props.title }</h1>; 165 | }; 166 | 167 | export default wire(Title, ['my-awesome-title'], title => ({ title })); 168 | ``` 169 | 170 | 如果我们仔细观察`Title.jsx`的话,我们会发现实际上用到的component和wiring后的component的可以来自于不同的文件,这样的话,所有的这些代码都是可以被很容易的测试的。(因为可以很容易被mock) 171 | -------------------------------------------------------------------------------- /patterns/22.event-handlers.md: -------------------------------------------------------------------------------- 1 | # 事件处理 2 | 我们需要在constructor中对于事件与对应的handler函数进行绑定. 3 | 4 | 大多数时候我们在发出DOM事件的组件内部写我们的handler函数. 5 | 在下面的例子中,我们在组件内部创建了一个click handler, 因为我们想所有的Swithcer Component当被点击时,做出同样的响应. 6 | 7 | ```javascript 8 | class Switcher extends React.Component { 9 | render() { 10 | return ( 11 | <button onClick={ this._handleButtonClick }> 12 | click me 13 | </button> 14 | ); 15 | } 16 | _handleButtonClick() { 17 | console.log('Button is clicked'); 18 | } 19 | } 20 | ``` 21 | 22 | 上面这样做完全没有问题,因为`_handleButtonClick`是一个函数, 我们把这个函数和onClick这个React支持的event绑定在了一起. 23 | 24 | 但是上面这样做也会带来问题, 使用function的写法, 会在function初始化时生成一个this. 比如我们在`_handleButtonClick`里面使用`this`, 此时的this是`_handleButtonClick`生成出来的, 和Switcher这个class的this没有任何关系, 如果我想访问类似于this.props 或者 this.state这样的对象, 代码便会报错. 25 | 26 | ```javascript 27 | class Switcher extends React.Component { 28 | constructor(props) { 29 | super(props); 30 | this.state = { name: 'React in patterns' }; 31 | } 32 | render() { 33 | return ( 34 | <button onClick={ this._handleButtonClick }> 35 | click me 36 | </button> 37 | ); 38 | } 39 | 40 | _handleButtonClick() { 41 | console.log(`Button is clicked inside ${ this.state.name }`); 42 | // 将导致 43 | // Uncaught TypeError: Cannot read property 'state' of null 44 | } 45 | } 46 | ``` 47 | 48 | 所以我们常用的解决办法像下面一样使用`bind` 49 | ```javascript 50 | <button onClick={ this._handleButtonClick.bind(this) }> 51 | click me 52 | </button> 53 | ``` 54 | 55 | 然而, 这种写法意味这我们要一次又一次的去调bind函数, 因为我们的button可能会被渲染很多次. 56 | 一种更好的做法是在组件的constructor中去做我们bind函数的调用. 57 | ```javascript 58 | class Switcher extends React.Component { 59 | constructor(props) { 60 | super(props); 61 | this.state = { name: 'React in patterns' }; 62 | this._buttonClick = this._handleButtonClick.bind(this); 63 | } 64 | render() { 65 | return ( 66 | <button onClick={ this._buttonClick }> 67 | click me 68 | </button> 69 | ); 70 | } 71 | _handleButtonClick() { 72 | console.log(`Button is clicked inside ${ this.state.name }`); 73 | } 74 | } 75 | ``` 76 | 77 | 另一种办法是使用箭头函数建立我们的handler函数, 因为箭头函数并不会创建`this`. 78 | 79 | 顺带一提的是, Facebook也推荐使用这种方法去处理需要访问组件的`this`的函数. 80 | 81 | 但是, 在constructor中去做binding也同样有用处. 比如, 我们可能会将父组件中定义的函数作为Props传下去. 因此在子组件中, 我们需要对这个函数进行bind. 82 | -------------------------------------------------------------------------------- /patterns/23.flux-pattern.md: -------------------------------------------------------------------------------- 1 | # Flux模式 2 | 3 | **简单的dispatcher实现** 4 | ```javascript 5 | var Dispatcher = function () { 6 | return { 7 | _stores: [], 8 | register: function (store) { 9 | this._stores.push({store: store}); 10 | }, 11 | dispatch: function (action) { 12 | if (this._stores.length > 0) { 13 | this._stores.forEach(function (entry) { 14 | entry.store.update(action); 15 | }); 16 | } 17 | } 18 | } 19 | }; 20 | ``` 21 | 我们期望我们的store能提供一个update方法. 接下来让我们来修改一下我们的register函数. 22 | ```javascript 23 | function register(store) { 24 | if (!store || !store.update && typeof store.update === 'function') { 25 | throw new Error('You should provide a store that has an update method'); 26 | } else { 27 | this._stores.push({store: store}); 28 | } 29 | } 30 | ``` 31 | **完整的dispatcher实现** 32 | ```javascript 33 | var Dispatcher = function () { 34 | return { 35 | _stores: [], 36 | register: function (store) { 37 | if (!store || !store.update && typeof store.update === 'function') { 38 | throw new Error('You should provide a store that has an `update` method.'); 39 | } else { 40 | var consumers = []; 41 | var change = function () { 42 | consumers.forEach(function (l) { 43 | l(store); 44 | }); 45 | }; 46 | var subscribe = function (consumer, noInit) { 47 | consumers.push(consumer); 48 | !noInit ? consumer(store) : null; 49 | }; 50 | 51 | this._stores.push({store: store, change: change}); 52 | return subscribe; 53 | } 54 | return false; 55 | }, 56 | dispatch: function (action) { 57 | if (this._stores.length > 0) { 58 | this._stores.forEach(function (entry) { 59 | entry.store.update(action, entry.change); 60 | }); 61 | } 62 | } 63 | } 64 | }; 65 | 66 | module.exports = { 67 | create: function () { 68 | var dispatcher = Dispatcher(); 69 | 70 | return { 71 | createAction: function (type) { 72 | if (!type) { 73 | throw new Error('Please, provide action\'s type.'); 74 | } else { 75 | return function (payload) { 76 | return dispatcher.dispatch({type: type, payload: payload}); 77 | } 78 | } 79 | }, 80 | createSubscriber: function (store) { 81 | return dispatcher.register(store); 82 | } 83 | } 84 | } 85 | }; 86 | ``` 87 | 88 | ### 参考资料: 89 | - https://github.com/krasimir/react-in-patterns/tree/master/patterns/flux 90 | -------------------------------------------------------------------------------- /patterns/24.one-way-data-flow.md: -------------------------------------------------------------------------------- 1 | # 单向数据流 2 | 3 | 单向数据流只关注于在store中维护的唯一的state,消除了不必要的多种states带来的复杂度. 4 | 这个store应该具有能被我们订阅(subscribe)store中变化的能力,实现如下: 5 | ```javascript 6 | var Store = { 7 | _handlers: [], 8 | _flag: '', 9 | onChange: function (handler) { 10 | this._handlers.push(handler); 11 | }, 12 | set: function (value) { 13 | this._flag = value; 14 | this._handlers.forEach(handler => handler()) 15 | }, 16 | get: function () { 17 | return this._flag; 18 | } 19 | }; 20 | ``` 21 | 然后我们会在我们的App组件上加上订阅我们的store的钩子, 当每次store发生改变的时候, 我们的组件就会被重新的渲染. 22 | ```javascript 23 | class App extends React.Component { 24 | constructor(props) { 25 | super(props); 26 | Store.onChange(this.forceUpdate.bind(this)); 27 | } 28 | 29 | render() { 30 | return ( 31 | <div> 32 | <Switcher 33 | value={ Store.get() } 34 | onChange={ Store.set.bind(Store) }/> 35 | </div> 36 | ); 37 | } 38 | } 39 | ``` 40 | 41 | 请注意我们使用了forceUpdate这个函数,但是事实上我们并不推荐使用这个函数. 42 | 43 | 一般情况下我们会使用高阶组件去帮我们处理重新渲染的事情,我们在这里使用forceUpdate函数只是想尽可能的保持我们这个例子简单. 44 | 45 | 因为我们使用了store, Swticher这个组件就变得超级简单了. 我们不需要再在Switcher组件内部再去维护一份State了: 46 | ```javascript 47 | class Switcher extends React.Component { 48 | constructor(props) { 49 | super(props); 50 | this._onButtonClick = e => { 51 | this.props.onChange(!this.props.value); 52 | } 53 | } 54 | 55 | render() { 56 | return ( 57 | <button onClick={ this._onButtonClick }> 58 | { this.props.value ? 'lights on' : 'lights off' } 59 | </button> 60 | ); 61 | } 62 | } 63 | ``` 64 | 使用单向数据流的好处是我们的组件在这种情况下变得非常声明式, 成为了所谓的纯UI组件, 只是我们Store里面数据的表达. 65 | React在诞生之初就是为了解决视图层(View层)的问题, 其核心思想也是从视图出发去解决问题. 我们用声明式的方式去构建我们的应用, 让我们的组件变得尽可能的木偶化,只关心我们的数据,将我们的复杂的数据逻辑交由我们的store去处理. 这也是我们去构建React应用的时候的一种比较好的思路. 66 | 67 | ### 参考资料: 68 | - https://www.startuprocket.com/articles/evolution-toward-one-way-data-flow-a-quick-introduction-to-redux 69 | -------------------------------------------------------------------------------- /patterns/25.presentational-vs-container.md: -------------------------------------------------------------------------------- 1 | # 展示组件和容器组件 2 | 3 | #### 问题描述 4 | UI和业务逻辑和数据混杂在一起. 5 | ```javascript 6 | class Clock extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = {time: this.props.time}; 10 | this._update = this._updateTime.bind(this); 11 | } 12 | 13 | render() { 14 | var time = this._formatTime(this.state.time); 15 | return ( 16 | <h1>{ time.hours } : { time.minutes } : { time.seconds }</h1> 17 | ); 18 | } 19 | 20 | componentDidMount() { 21 | this._interval = setInterval(this._update, 1000); 22 | } 23 | 24 | componentWillUnmount() { 25 | clearInterval(this._interval); 26 | } 27 | 28 | _formatTime(time) { 29 | var [ hours, minutes, seconds ] = [ 30 | time.getHours(), 31 | time.getMinutes(), 32 | time.getSeconds() 33 | ].map(num => num < 10 ? '0' + num : num); 34 | 35 | return {hours, minutes, seconds}; 36 | } 37 | 38 | _updateTime() { 39 | this.setState({time: new Date(this.state.time.getTime() + 1000)}); 40 | } 41 | } 42 | 43 | ReactDOM.render(<Clock time={ new Date() }/>, ...); 44 | ``` 45 | #### 解决办法. 46 | 47 | 我们将组件拆分成容器(container)组件和UI(presentation)组件 48 | 49 | #### 容器组件 50 | 容器组件关心数据(包括数据的格式和数据的来源等). 容器组件关心具体的业务逻辑, 它接收数据并将数据整理成我们的UI组件需要的格式传递给UI组件. 51 | 我们常使用高阶组件去建立容器组件. 52 | 一般情况下, 容器组件的render方法里面包含的只会是UI组件. 53 | 54 | ```javascript 55 | // Clock/index.js 56 | import Clock from './Clock.jsx'; // <-- Clock是一个UI组件 57 | 58 | export default class ClockContainer extends React.Component { 59 | constructor(props) { 60 | super(props); 61 | this.state = {time: props.time}; 62 | this._update = this._updateTime.bind(this); 63 | } 64 | 65 | render() { 66 | return <Clock { ...this._extract(this.state.time) }/>; 67 | } 68 | 69 | componentDidMount() { 70 | this._interval = setInterval(this._update, 1000); 71 | } 72 | 73 | componentWillUnmount() { 74 | clearInterval(this._interval); 75 | } 76 | 77 | _extract(time) { 78 | return { 79 | hours: time.getHours(), 80 | minutes: time.getMinutes(), 81 | seconds: time.getSeconds() 82 | }; 83 | } 84 | 85 | _updateTime() { 86 | this.setState({time: new Date(this.state.time.getTime() + 1000)}); 87 | } 88 | }; 89 | ``` 90 | #### UI组件 91 | UI组件关心组件展示出来是什么样子. UI组件一般由基本的html标签为基础, 用以在页面上展示. 92 | 理想的UI组件应该被设计为没有外部依赖的组件. 常用的实现是使用没有内部state的无状态组件(stateless function) 93 | 94 | ```javascript 95 | // Clock/Clock.jsx 96 | export default function Clock(props) { 97 | var [ hours, minutes, seconds ] = [ 98 | props.hours, 99 | props.minutes, 100 | props.seconds 101 | ].map(num => num < 10 ? '0' + num : num); 102 | 103 | return <h1>{ hours } : { minutes } : { seconds }</h1>; 104 | }; 105 | ``` 106 | 容器组件封装了封装了业务逻辑, 并且可以灵活的讲不同的数据注入不同的UI组件中, 这是使用容器组件带来明显的好处. 107 | 常见使用容器组件的方法是我们不去在容器组件内部规定哪个UI组件将被渲染, 而是建立一个接收一个UI组件的函数, 这样非常灵活, 让我们的容器组件可以包裹任意UI组件. 108 | 比如, 和使用下面的形式相比: 109 | 110 | ```javascript 111 | import Clock from './Clock.jsx'; 112 | export default class ClockContainer extends React.Component { 113 | render() { 114 | return <Clock />; 115 | } 116 | } 117 | ``` 118 | 119 | 我们更推荐下面这种写法: 120 | 121 | ```javascript 122 | export default function (Component) { 123 | return class Container extends React.Component { 124 | render() { 125 | return <Component />; 126 | } 127 | } 128 | } 129 | ``` 130 | 使用这种方法,我们的容器就编程了能渲染任意UI组件的容器,非常的灵活. 131 | 回到时钟的例子, 如果这时候你想把数显时钟变成一个指针时钟, 只需要替换容器组件内部的UI时钟组件就可以了. 132 | 133 | ### 参考资料: 134 | - https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.mbglcakmp 135 | - https://github.com/krasimir/react-in-patterns/tree/master/patterns/presentational-and-container 136 | - https://medium.com/@learnreact/container-components-c0e67432e005 137 | -------------------------------------------------------------------------------- /patterns/26.third-party-integration.md: -------------------------------------------------------------------------------- 1 | # 三方集成 2 | 下面这篇教程会简单介绍React如何和三方库集成. 3 | 4 | 在这个例子中我们将会学习到如何混合使用React和jQuery UI 这个插件. 5 | 我们选用了tag-it这个jQuery插件来举例. 这个插件将无序列表(unordered list)转化成input标签来管理. 6 | ```html 7 | <ul> 8 | <li>JavaScript</li> 9 | <li>CSS</li> 10 | </ul> 11 | ``` 12 | 为了让上面这段代码能够运行, 我们需要引入jQuery, jQuery UI 以及tag-it这个插件. 使用插件的代码如下. 13 | ```javascript 14 | $('<dom element selector>').tagit(); 15 | ``` 16 | 我们选择了一个DOM元素然后调用了tagit方法. 17 | 18 | 首先我们要做的事情是对Tags这个组件我们需要它只被React渲染一次(single-renderer). 19 | 这是因为当React渲染出我们想控制的DOM元素之后,我们想把该元素的控制权从React转交给jQuery. 20 | 如果我们跳过了这一步,那么React和jQuery会对相同的DOM元素进行控制, 并且不会知道彼此的存在. 为了实现这个一次渲染的机制, 我们需要用到React自带的生命周期方法`shouldComponentUpdate`. 21 | 22 | 当我们想以编程的方式在已有的tag-itDOM对象上添加新的标签时, 这一行为将被这个React组件触发, 并且需要配合上jQuery API一起才能工作. 我们需要找到一种方法, 既能让数据和Tags组件交互,又能保证组件只渲染一次. 为了更形象的描述我们的实现过程, 我们会在我们的APP组件里面添加一个input和一个button. 当button被点击时, 我们会将一个string传递到Tags组件中. 23 | 24 | ```javascript 25 | class App extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | 29 | this._addNewTag = this._addNewTag.bind(this); 30 | this.state = { 31 | tags: ['JavaScript', 'CSS'], 32 | newTag: null 33 | }; 34 | } 35 | 36 | _addNewTag() { 37 | this.setState({newTag: this.refs.field.value}); 38 | } 39 | 40 | render() { 41 | return ( 42 | <div> 43 | <p>Add new tag:</p> 44 | <div> 45 | <input type='text' ref='field'/> 46 | <button onClick={ this._addNewTag }>Add</button> 47 | </div> 48 | <Tags tags={ this.state.tags } newTag={ this.state.newTag }/> 49 | </div> 50 | ); 51 | } 52 | } 53 | ``` 54 | 我们使用了组件内部的state来存储我们新加入的field. 当我们每一次点击button的时候, state都会被更新从而触发Tags组件的重新渲染. 55 | 然而, 因为在shouldComponentUpdate中我们返回了false, 所以组件事实上并不会被更新. 56 | 还有另外一点不同的是我们通过另一个生命周期方法componentWillReceiveProps取到了新标签的值, 同时调用tagit方法来增加我们的filed. 57 | ```javascript 58 | class Tags extends React.Component { 59 | componentDidMount() { 60 | this.list = $(this.refs.list); 61 | this.list.tagit(); 62 | } 63 | 64 | shouldComponentUpdate() { 65 | return false; 66 | } 67 | 68 | componentWillReceiveProps(newProps) { 69 | this.list.tagit('createTag', newProps.newTag); 70 | } 71 | 72 | render() { 73 | return ( 74 | <ul ref='list'> 75 | { this.props.tags.map((tag, i) => <li key={ i }>{ tag } </li>) } 76 | </ul> 77 | ); 78 | } 79 | } 80 | ``` 81 | ### 参考资料: 82 | - https://github.com/krasimir/react-in-patterns/tree/master/patterns/third-party 83 | -------------------------------------------------------------------------------- /patterns/27.passing-function-to-setState.md: -------------------------------------------------------------------------------- 1 | # 给setState传入回调函数 2 | 3 | 在[async-nature-of-setState]("./19.async-nature-of-setState.md")中我们已经提到过, setState其实是异步的. 因为出于性能优化考虑, React会将多次setState做一次批处理. 于是setState并不会在被调用之后立即改变我们的state. 4 | 这就意味着你并不能依赖于在调用setState方法之后state, 因为此时你并不能确认该state更新与否. 5 | 当然针对这个问题我们也有解决办法--用前一个state(previous state)作为需要传入函数的参数,将一个函数作为第二个参数传递给setState. 这样做能保证你传入的函数需要取到的state一定会是被传入的setState执行之后的state. 6 | 7 | #### 问题 8 | ```javascript 9 | // assuming this.state.count === 0 10 | this.setState({count: this.state.count + 1}); 11 | this.setState({count: this.state.count + 1}); 12 | this.setState({count: this.state.count + 1}); 13 | // this.state.count === 1, not 3 14 | ``` 15 | #### 解决办法 16 | ```javascript 17 | this.setState((prevState, props) => ({ 18 | count: prevState.count + props.increment 19 | })); 20 | ``` 21 | 22 | #### 举一反三 23 | ```javascript 24 | // Passing object 25 | this.setState({ expanded: !this.state.expanded }); 26 | 27 | // Passing function 28 | this.setState(prevState => ({ expanded: !prevState.expanded })); 29 | ``` 30 | 31 | ### 参考资料: 32 | - [setState() Gate](https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82) 33 | - [Do I need to use setState(function) overload in this case?](http://stackoverflow.com/questions/43428456/do-i-need-to-use-setstatefunction-overload-in-this-case/43440790#43440790) 34 | - [Functional setState is the future of React](https://medium.freecodecamp.com/functional-setstate-is-the-future-of-react-374f30401b6b) 35 | -------------------------------------------------------------------------------- /patterns/28.decorators.md: -------------------------------------------------------------------------------- 1 | # 装饰器 2 | 3 | 装饰器(Decorators)(*被babel支持, 在 03/17 之后作为stage-2的proposal被引入*) 4 | 5 | 如果你在使用类似于mobx的库, 你能够使用装饰器装饰你的函数. 装饰器本质上其实就是将组件传入一个函数. 使用装饰器能让组件更灵活,更可读并且更易修改组件的功能. 6 | 7 | 不使用装饰器的例子 8 | ```javascript 9 | class ProfileContainer extends Component { 10 | // Component code 11 | } 12 | export default observer(ProfileContainer) 13 | ``` 14 | 使用装饰器的例子 15 | ```javascript 16 | @observer 17 | export default class ProfileContainer extends Component { 18 | // Component code 19 | } 20 | ``` 21 | 22 | ### 相关文章: 23 | - [Enhancing React components with Decorators](https://medium.com/@gigobyte/enhancing-react-components-with-decorators-441320e8606a) 24 | 25 | ### 参考资料: 26 | - [Decorators != higher ordered components](https://twitter.com/dan_abramov/status/628202050946514944) 27 | - [React Decorator example - Module](https://github.com/gigobyte/react-document-title-decorator) 28 | - [What is the use of Connect(decorator in react-redux)](http://stackoverflow.com/questions/36553814/what-is-the-use-of-connect-decorator-in-react-redux) 29 | - [Decorators with React Components](http://stackoverflow.com/questions/36286384/decorators-with-react-components) 30 | - [Exploring ES7 decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.8cbzw5wcl) 31 | - [Understanding Decorators](https://survivejs.com/react/appendices/understanding-decorators/) 32 | -------------------------------------------------------------------------------- /patterns/29.feature-flags-using-redux.md: -------------------------------------------------------------------------------- 1 | # 功能开关 2 | 3 | 使用Redux在React中实现Feature标记符 4 | 5 | ```javascript 6 | // createFeatureFlaggedContainer.js 7 | import React from 'react'; 8 | import { connect } from 'react-redux'; 9 | import { isFeatureEnabled } from './reducers' 10 | 11 | export default function createFeatureFlaggedContainer({ 12 | featureName, 13 | enabledComponent, 14 | disabledComponent 15 | }) { 16 | function FeatureFlaggedContainer({ isEnabled, ...props }) { 17 | const Component = isEnabled ? enabledComponent : disabledComponent; 18 | 19 | if (Component) { 20 | return <Component {...props} />; 21 | } 22 | 23 | // `disabledComponent` is optional property 24 | return null; 25 | } 26 | 27 | // Having `displayName` is very useful for debugging. 28 | FeatureFlaggedContainer.displayName = `FeatureFlaggedContainer(${ featureName })`; 29 | 30 | return connect((store) => { 31 | isEnabled: isFeatureEnabled(store, featureName) 32 | })(FeatureFlaggedContainer); 33 | } 34 | ``` 35 | ```javascript 36 | // EnabledFeature.js 37 | import { connect } from 'react-redux'; 38 | import { isFeatureEnabled } from './reducers' 39 | 40 | function EnabledFeature({ isEnabled, children }) { 41 | if (isEnabled) { 42 | return children; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | export default connect((store, { name }) => { 49 | isEnabled: isFeatureEnabled(store, name) 50 | })(EnabledFeature); 51 | ``` 52 | ```javascript 53 | // featureEnabled.js 54 | import createFeatureFlaggedContainer from './createFeatureFlaggedContainer' 55 | 56 | // Decorator for "Page" components. 57 | // usage: enabledFeature('unicorns')(UnicornsPage); 58 | export default function enabledFeature(featureName) { 59 | return (Component) => { 60 | return createFeatureFlaggedContainer({ 61 | featureName, 62 | enabledComponent: Component, 63 | disabledComponent: PageNotFound, // 404 page or something similar 64 | }); 65 | }; 66 | }; 67 | ``` 68 | ```javascript 69 | // features.js 70 | // This is quite simple reducer, containing only an array of features. 71 | // You can attach this data to a `currentUser` or similar reducer. 72 | 73 | // `BOOTSTAP` is global action, which contains the initial data for a page 74 | // Features access usually don't change during user usage of a page 75 | const BOOTSTAP = 'features/receive'; 76 | 77 | export default function featuresReducer(state, { type, payload }) { 78 | if (type === BOOTSTAP) { 79 | return payload.features || []; 80 | } 81 | 82 | return state || []; 83 | } 84 | 85 | export function isFeatureEnabled(features, featureName) { 86 | return features.indexOf(featureName) !== -1; 87 | } 88 | ``` 89 | ```javascript 90 | // reducers.js 91 | // This is your main reducer.js file 92 | import { combineReducers } from 'redux'; 93 | 94 | import features, { isFeatureEnabled as isFeatureEnabledSelector } from './features'; 95 | // ...other reducers 96 | 97 | export default combineReducers({ 98 | features 99 | // ...other reducers 100 | }); 101 | 102 | // This is the important part, access to `features` reducer should only happens 103 | // via this selector. 104 | // Then you can always change where/how the features are stored. 105 | export function isFeatureEnabled({ features }, featureName) { 106 | return isFeatureEnabledSelector(features, featureName); 107 | } 108 | ``` 109 | ### 参考资料: 110 | - [Feature flags in React](http://blog.rstankov.com/feature-flags-in-react/) 111 | - [Gist](https://gist.github.com/RStankov/0e764f27daf38f2fcd81b82360334528) 112 | -------------------------------------------------------------------------------- /patterns/30.component-switch.md: -------------------------------------------------------------------------------- 1 | # 组件切换 2 | 3 | 一个可切换的组件实际上是包含了多个组件, 选择渲染其中某个组件的组件. 4 | 我们使用对象来将props的值和组件做上映射. 5 | 6 | ```javascript 7 | import HomePage from './HomePage.jsx'; 8 | import AboutPage from './AboutPage.jsx'; 9 | import UserPage from './UserPage.jsx'; 10 | import FourOhFourPage from './FourOhFourPage.jsx'; 11 | 12 | const PAGES = { 13 | home: HomePage, 14 | about: AboutPage, 15 | user: UserPage 16 | }; 17 | 18 | const Page = (props) => { 19 | const Handler = PAGES[props.page] || FourOhFourPage; 20 | 21 | return <Handler {...props} /> 22 | }; 23 | 24 | // The keys of the PAGES object can be used in the prop types to catch dev-time errors. 25 | Page.propTypes = { 26 | page: PropTypes.oneOf(Object.keys(PAGES)).isRequired 27 | }; 28 | ``` 29 | 30 | ### 参考资料: 31 | - https://hackernoon.com/10-react-mini-patterns-c1da92f068c5 32 | -------------------------------------------------------------------------------- /patterns/31.reaching-into-a-component.md: -------------------------------------------------------------------------------- 1 | # 深入某个组件内部 2 | 3 | 通过父组件去访问子组件. 4 | 比如一个能自动focus的输入框(通过父组件控制自动focus) 5 | 6 | #### 子组件 7 | 子组件是一个带有input标签和focus方法的组件. 其中focus方法能focus到对应的HTML元素上. 8 | ```javascript 9 | class Input extends Component { 10 | focus() { 11 | this.el.focus(); 12 | } 13 | 14 | render() { 15 | return ( 16 | <input 17 | ref={el=> { this.el = el; }} 18 | /> 19 | ); 20 | } 21 | } 22 | ``` 23 | #### 父组件 24 | 在父组件中,我们能得到子组件的引用并且调用子组件的focus方法. 25 | ```javascript 26 | class SignInModal extends Component { 27 | componentDidMount() { 28 | // Note that when you use ref on a component, it’s a reference to 29 | // the component (not the underlying element), so you have access to its methods. 30 | this.InputComponent.focus(); 31 | } 32 | 33 | render() { 34 | return ( 35 | <div> 36 | <label>User name:</label> 37 | <Input 38 | ref={comp => { this.InputComponent = comp; }} 39 | /> 40 | </div> 41 | ) 42 | } 43 | } 44 | ``` 45 | ### 参考资料: 46 | - https://hackernoon.com/10-react-mini-patterns-c1da92f068c5 47 | 48 | -------------------------------------------------------------------------------- /patterns/32.list-components.md: -------------------------------------------------------------------------------- 1 | # 集合组件 2 | 3 | 列表和其他类型的集合某种程度上也可以用组件来描述. 4 | 5 | 为了避免完全给列表新建一个单独的组件, 我们可以使用以下这种写法. 6 | ```javascript 7 | const SearchSuggestions = (props) => { 8 | // renderSearchSuggestion() behaves as a pseudo SearchSuggestion component 9 | // keep it self contained and it should be easy to extract later if needed 10 | const renderSearchSuggestion = listItem => ( 11 | <li key={listItem.id}>{listItem.name} {listItem.id}</li> 12 | ); 13 | 14 | return ( 15 | <ul> 16 | {props.listItems.map(renderSearchSuggestion)} 17 | </ul> 18 | ); 19 | }; 20 | ``` 21 | 22 | 如果你想在更复杂的场景里面或者其他什么地方使用这个组件, 你能很轻松的复制这段代码到新的组件中. (不要过度设计组件) 23 | If things get more complex or you want to use this component elsewhere, 24 | you should be able to copy/paste the code out into a new component. 25 | Don’t prematurely componentize. 26 | 27 | ### 参考资料: 28 | - https://hackernoon.com/10-react-mini-patterns-c1da92f068c5 29 | 30 | -------------------------------------------------------------------------------- /patterns/33.format-text-via-component.md: -------------------------------------------------------------------------------- 1 | # 用来做format的组件 2 | 3 | 在我们需要格式化字符串的时候, 我们可以在render方法中去使用helper函数. 然而, 更好的方法是去使用一个组件去做这件事. 4 | 5 | #### 使用组件的做法 6 | render函数的实现会更加的干净因为只会是一些组件的组合. 7 | ```javascript 8 | const Price = (props) => { 9 | // toLocaleString 并不是React的API而是原生JavaScript的内置方法 10 | // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString 11 | const price = props.children.toLocaleString('en', { 12 | style: props.showSymbol ? 'currency' : undefined, 13 | currency: props.showSymbol ? 'USD' : undefined, 14 | maximumFractionDigits: props.showDecimals ? 2 : 0 15 | }); 16 | 17 | return <span className={props.className}>{price}</span> 18 | }; 19 | 20 | Price.propTypes = { 21 | className: PropTypes.string, 22 | children: PropTypes.number, 23 | showDecimals: PropTypes.bool, 24 | showSymbol: PropTypes.bool 25 | }; 26 | 27 | Price.defaultProps = { 28 | children: 0, 29 | showDecimals: true, 30 | showSymbol: true, 31 | }; 32 | 33 | const Page = () => { 34 | const lambPrice = 1234.567; 35 | const jetPrice = 999999.99; 36 | const bootPrice = 34.567; 37 | 38 | return ( 39 | <div> 40 | <p>One lamb is <Price className="expensive">{lambPrice}</Price></p> 41 | <p>One jet is <Price showDecimals={false}>{jetPrice}</Price></p> 42 | <p>Those gumboots will set ya back 43 | <Price 44 | showDecimals={false} 45 | showSymbol={false}> 46 | {bootPrice} 47 | </Price> 48 | bucks. 49 | </p> 50 | </div> 51 | ); 52 | }; 53 | ``` 54 | #### 不使用组件的做法. 55 | 代码量更少, 但是让render方法看起来不那么干净(作者个人感觉, 哈哈) 56 | ```javascript 57 | function numberToPrice(num, options = {}) { 58 | const showSymbol = options.showSymbol !== false; 59 | const showDecimals = options.showDecimals !== false; 60 | 61 | return num.toLocaleString('en', { 62 | style: showSymbol ? 'currency' : undefined, 63 | currency: showSymbol ? 'USD' : undefined, 64 | maximumFractionDigits: showDecimals ? 2 : 0 65 | }); 66 | } 67 | 68 | const Page = () => { 69 | const lambPrice = 1234.567; 70 | const jetPrice = 999999.99; 71 | const bootPrice = 34.567; 72 | 73 | return ( 74 | <div> 75 | <p>One lamb is <span className="expensive">{numberToPrice(lambPrice)}</span></p> 76 | <p>One jet is {numberToPrice(jetPrice, { showDecimals: false })}</p> 77 | <p>Those gumboots will set ya back 78 | {numberToPrice(bootPrice, { showDecimals: false, showSymbol: false })} 79 | bucks.</p> 80 | </div> 81 | ); 82 | }; 83 | ``` 84 | 85 | ### 参考资料: 86 | - [10 React Mini Patterns](https://hackernoon.com/10-react-mini-patterns-c1da92f068c5) 87 | 88 | -------------------------------------------------------------------------------- /patterns/34.share-tracking-logic.md: -------------------------------------------------------------------------------- 1 | # 共享tracking的逻辑 2 | 3 | 使用高阶组件能在很多的UI组件中追踪逻辑. 4 | 例如: 在许多组件中加入分析数据的追踪. 5 | 6 | eg. Adding analytics tracking across various components. 7 | 8 | - 一次实现,多次使用 9 | - 易于剔除, 我们的组件还是能保持很好的测试性,不会被这个tracking组件影响. 10 | 11 | ```javascript 12 | import tracker from './tracker.js'; 13 | 14 | // 高阶组件 15 | const pageLoadTracking = (ComposedComponent) => class HOC extends Component { 16 | componentDidMount() { 17 | tracker.trackPageLoad(this.props.trackingData); 18 | } 19 | 20 | componentDidUpdate() { 21 | tracker.trackPageLoad(this.props.trackingData); 22 | } 23 | 24 | render() { 25 | return <ComposedComponent {...this.props} /> 26 | } 27 | }; 28 | 29 | // 用法 30 | import LoginComponent from "./login"; 31 | 32 | const LoginWithTracking = pageLoadTracking(LoginComponent); 33 | 34 | class SampleComponent extends Component { 35 | render() { 36 | const trackingData = {/** Nested Object **/}; 37 | return <LoginWithTracking trackingData={trackingData}/> 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /perf-tips/01.shouldComponentUpdate-check.md: -------------------------------------------------------------------------------- 1 | # shouldComponentUpdate检查 2 | 3 | 合理的实现`shouldComponentUpdate`能够避免不必要的重新渲染. 4 | 5 | React会在props和state发生改变的时候重新渲染组件. 6 | 试想一下当每次有props和state发生改变时(可能只是一个很小的用户动作), 整个页面都会重新渲染一次, 这从性能上来说肯定不能令人满意. 这就是`shouldComponentUpdate`这个生命周期函数发挥作用的时候了. 当React想要重新渲染组件时, React会检查`shouldComponentUpdate`这个函数是返回true还是false(这将决定组件是否更新.)React默认这个函数是返回true的,意味着无论state还是props发生变化, 组件都将被更新). 所以对于那些不需要变化的组件, 我们可以直接返回false来阻止组件更新,以此提升性能. 但更多的时候, 我们需要在这个函数内写自己的逻辑来判断组件是否需要更新. 7 | 8 | #### 坏实践 9 | ```javascript 10 | const AutocompleteItem = (props) => { 11 | const selectedClass = props.selected === true ? "selected" : ""; 12 | var path = parseUri(props.url).path; 13 | path = path.length <= 0 ? props.url : "..." + path; 14 | 15 | return ( 16 | <li 17 | onMouseLeave={props.onMouseLeave} 18 | className={selectedClass}> 19 | <i className="ion-ios-eye" 20 | data-image={props.image} 21 | data-url={props.url} 22 | data-title={props.title} 23 | onClick={props.handlePlanetViewClick}/> 24 | <span 25 | onMouseEnter={props.onMouseEnter} 26 | > 27 | <div className="dot bg-mint"/> 28 | {path} 29 | </span> 30 | </li> 31 | ); 32 | }; 33 | ``` 34 | #### 好实践 35 | ```javascript 36 | export default class AutocompleteItem extends React.Component { 37 | shouldComponentUpdate(nextProps, nextState) { 38 | if ( 39 | nextProps.url !== this.props.url || 40 | nextProps.selected !== this.props.selected 41 | ) { 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | render() { 48 | const {props} = this; 49 | const selectedClass = props.selected === true ? "selected" : ""; 50 | var path = parseUri(props.url).path; 51 | path = path.length <= 0 ? props.url : "..." + path; 52 | 53 | return ( 54 | <li 55 | onMouseLeave={props.onMouseLeave} 56 | className={selectedClass}> 57 | <i className="ion-ios-eye" 58 | data-image={props.image} 59 | data-url={props.url} 60 | data-title={props.title} 61 | onClick={props.handlePlanetViewClick}/> 62 | <span 63 | onMouseEnter={props.onMouseEnter}> 64 | <div className="dot bg-mint"/> 65 | {path} 66 | </span> 67 | </li> 68 | ); 69 | } 70 | } 71 | ``` 72 | 73 | ### 参考资料: 74 | - [React Performance optimization](https://medium.com/@nesbtesh/react-performance-optimization-28ec5b61fff3) 75 | - [React rendering misconception](https://robots.thoughtbot.com/react-rendering-misconception) 76 | -------------------------------------------------------------------------------- /perf-tips/02.pure-component.md: -------------------------------------------------------------------------------- 1 | # 使用Pure Component 2 | Pure Component默认会在`shouldComponentUpdate`方法中做浅比较. 这种实现可以避免可以避免发生在state或者props没有真正改变的重新渲染. 3 | 4 | Recompose提供了一个叫`pure`的高阶组件来实现这个功能, React在v15.3.0中正式加入了`React.PureComponent`. 5 | 6 | #### 坏实践 7 | ```javascript 8 | export default (props, context) => { 9 | // ... do expensive compute on props ... 10 | return <SomeComponent {...props} /> 11 | } 12 | ``` 13 | 14 | #### 好实践 15 | ```javascript 16 | import { pure } from 'recompose'; 17 | // This won't be called when the props DONT change 18 | export default pure((props, context) => { 19 | // ... do expensive compute on props ... 20 | return <SomeComponent someProp={props.someProp}/> 21 | }) 22 | ``` 23 | 24 | #### 更好的写法 25 | ```javascript 26 | // This is better mainly because it uses no external dependencies. 27 | import { PureComponent } from 'react'; 28 | 29 | export default class Example extends PureComponent { 30 | // This won't re-render when the props DONT change 31 | render() { 32 | // ... do expensive compute on props ... 33 | return <SomeComponent someProp={props.someProp}/> 34 | } 35 | } 36 | }) 37 | ``` 38 | 39 | ### 参考资料: 40 | - [Recompose](https://github.com/acdlite/recompose#composition) 41 | - [Higher Order Components with Functional Patterns Using Recompose](https://egghead.io/courses/higher-order-components-with-functional-patterns-using-recompose) 42 | - [React: PureComponent](https://facebook.github.io/react/docs/react-api.html#react.purecomponent) 43 | - [Pure Components](https://www.fullstackreact.com/30-days-of-react/day-11/) 44 | - [Top 5 Recompose HOCs](https://medium.com/@abhiaiyer/top-5-recompose-hocs-1a4c9cc4566) 45 | -------------------------------------------------------------------------------- /perf-tips/03.reselect.md: -------------------------------------------------------------------------------- 1 | # 使用 Reselect 2 | 在React-Redux的 connect(mapState)中使用Reselect, 这能避免频繁的重新渲染的发生. 3 | 4 | #### 坏实践 5 | ```javascript 6 | let App = ({otherData, resolution}) => ( 7 | <div> 8 | <DataContainer data={otherData}/> 9 | <ResolutionContainer resolution={resolution}/> 10 | </div> 11 | ); 12 | 13 | const doubleRes = (size) => ({ 14 | width: size.width * 2, 15 | height: size.height * 2 16 | }); 17 | 18 | App = connect(state => { 19 | return { 20 | otherData: state.otherData, 21 | resolution: doubleRes(state.resolution) 22 | } 23 | })(App); 24 | ``` 25 | 26 | 在上面的代码中, 一旦otherData发生修改, 那么DataContainer和ResolutionContainer两者都会发生重新渲染, 即使在resolution的state并没有发生改变时也是这样. 27 | 这是因为doubleRes这个函数总是会返回一个新的resolutiond对象的实体. 28 | 如果doubleRes函数通过Reselect方式编写就没有这个问题了. 29 | Reslect会记录下上一次函数调用的结果并且当再次以相同方式调用时返回相同的结果(而不是创建一个一模一样的新结果). 只有当传入的参数不同时, 才会产生新的结果. 30 | 31 | #### 好实践 32 | ```javascript 33 | import { createSelector } from 'reselect'; 34 | const doubleRes = createSelector( 35 | r => r.width, 36 | r => r.height, 37 | (width, height) => ({ 38 | width: width * 2, 39 | height: height * 2 40 | }) 41 | ); 42 | ``` 43 | ### 参考资料: 44 | - [React](https://medium.com/@esamatti/react-js-pure-render-performance-anti-pattern-fb88c101332f#.cz2ypc2ob) 45 | - [Computing Derived Data: Docs](http://redux.js.org/docs/recipes/ComputingDerivedData.html) 46 | -------------------------------------------------------------------------------- /perf-tips/README.md: -------------------------------------------------------------------------------- 1 | # Perf Tips 2 | 3 | **基本准则** 4 | 5 | - 在`shouldComponentUpdate`中避免不必要的检查. 6 | 7 | - 使用不可变数据类型(Immutable). 8 | 9 | - 编写针对产品环境的打包配置(Production Build). 10 | 11 | - 通过Chrome Timeline来记录组件所耗费的资源. 12 | 13 | - 在`componentWillMount`或者`componentDidMount`里面通过`setTimeOut`或者`requestAnimationFram`来延迟执行那些需要大量计算的任务. 14 | 15 | ## 相关文章 16 | 17 | [Optimizing Performance: Docs](https://facebook.github.io/react/docs/optimizing-performance.html) 18 | 19 | [Performance Engineering with React](http://benchling.engineering/performance-engineering-with-react/) 20 | 21 | [Tips to optimise rendering of a set of elements in React](https://blog.lavrton.com/how-to-optimise-rendering-of-a-set-of-elements-in-react-ad01f5b161ae) 22 | 23 | [React.js Best Practices for 2016](https://blog.risingstack.com/react-js-best-practices-for-2016/) 24 | -------------------------------------------------------------------------------- /styling/01.stateless-ui-components.md: -------------------------------------------------------------------------------- 1 | # 给无状态的纯UI组件应用样式 2 | 请保持样式远离那些离不开state的组件. 比如路由, 视图, 容器, 表单, 布局等等不应该有任何的样式或者css class出现在组件上. 3 | 相反, 这些复杂的业务组件应该有一些带有基本功能的无状态UI组件组成. 4 | 5 | 没有任何样式/classNames的Form组件, 仅有纯的组件组合而成. 6 | ```javascript 7 | class SampleComponent extends Component { 8 | render() { 9 | return ( 10 | <form onSubmit={this.handleSubmit}> 11 | <Heading children='Sign In'/> 12 | <Input 13 | name='username' 14 | value={username} 15 | onChange={this.handleChange}/> 16 | <Input 17 | type='password' 18 | name='password' 19 | value={password} 20 | onChange={this.handleChange}/> 21 | <Button 22 | type='submit' 23 | children='Sign In'/> 24 | </form> 25 | ) 26 | } 27 | } 28 | 29 | // 表达组件(带样式) 30 | const Button = ({ 31 | ...props 32 | }) => { 33 | const sx = { 34 | fontFamily: 'inherit', 35 | fontSize: 'inherit', 36 | fontWeight: 'bold', 37 | textDecoration: 'none', 38 | display: 'inline-block', 39 | margin: 0, 40 | paddingTop: 8, 41 | paddingBottom: 8, 42 | paddingLeft: 16, 43 | paddingRight: 16, 44 | border: 0, 45 | color: 'white', 46 | backgroundColor: 'blue', 47 | WebkitAppearance: 'none', 48 | MozAppearance: 'none' 49 | } 50 | 51 | return ( 52 | <button {...props} style={sx}/> 53 | ) 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /styling/02.styles-module.md: -------------------------------------------------------------------------------- 1 | # 样式模块(style module) 2 | 一般来说, 在组件内写死(hard code)样式应该是要被避免的. 3 | 这些有可能被不同的UI组件分享的样式应该被分开放入对应的模块中. 4 | 5 | ```javascript 6 | // 样式模块 7 | export const white = '#fff'; 8 | export const black = '#111'; 9 | export const blue = '#07c'; 10 | 11 | export const colors = { 12 | white, 13 | black, 14 | blue 15 | }; 16 | 17 | export const space = [ 18 | 0, 19 | 8, 20 | 16, 21 | 32, 22 | 64 23 | ]; 24 | 25 | const styles = { 26 | bold: 600, 27 | space, 28 | colors 29 | }; 30 | 31 | export default styles 32 | ``` 33 | 34 | #### Usage 35 | ```javascript 36 | // button.jsx 37 | import React from 'react' 38 | import { bold, space, colors } from './styles' 39 | 40 | const Button = ({ 41 | ...props 42 | }) => { 43 | const sx = { 44 | fontFamily: 'inherit', 45 | fontSize: 'inherit', 46 | fontWeight: bold, 47 | textDecoration: 'none', 48 | display: 'inline-block', 49 | margin: 0, 50 | paddingTop: space[1], 51 | paddingBottom: space[1], 52 | paddingLeft: space[2], 53 | paddingRight: space[2], 54 | border: 0, 55 | color: colors.white, 56 | backgroundColor: colors.blue, 57 | WebkitAppearance: 'none', 58 | MozAppearance: 'none' 59 | }; 60 | 61 | return ( 62 | <button {...props} style={sx}/> 63 | ) 64 | }; 65 | ``` 66 | -------------------------------------------------------------------------------- /styling/03.style-functions.md: -------------------------------------------------------------------------------- 1 | # 样式函数(Style Functions) 2 | 因为在React中可以很方便的使用JavaScript, 所以我们能使用helper函数来帮我们处理样式相关的问题. 3 | #### 第一个例子 4 | 5 | 一个用rgba格式来创造黑色的函数. 6 | ```javascript 7 | const darken = (n) => `rgba(0, 0, 0, ${n})`; 8 | darken(1 / 8); // 'rgba(0, 0, 0, 0.125)' 9 | 10 | const shade = [ 11 | darken(0), 12 | darken(1 / 8), 13 | darken(1 / 4), 14 | darken(3 / 8), 15 | darken(1 / 2), 16 | darken(5 / 8), 17 | darken(3 / 4), 18 | darken(7 / 8), 19 | darken(1) 20 | ]; 21 | // 现在, 22 | // shade[4] 就是 'rgba(0, 0, 0, 0.5)' 23 | ``` 24 | 25 | #### 第二个例子 26 | 27 | 给margin 和 padding创建一个比例来保持视觉节奏的一致. 28 | ```javascript 29 | // Modular powers of two scale 30 | const scale = [ 31 | 0, 32 | 8, 33 | 16, 34 | 32, 35 | 64 36 | ]; 37 | 38 | // 通过这个函数去取得一部分的样式 39 | const createScaledPropertyGetter = (scale) => (prop) => (x) => { 40 | return (typeof x === 'number' && typeof scale[x] === 'number') 41 | ? {[prop]: scale[x]} 42 | : null 43 | }; 44 | const getScaledProperty = createScaledPropertyGetter(scale); 45 | 46 | export const getMargin = getScaledProperty('margin'); 47 | export const getPadding = getScaledProperty('padding'); 48 | // 样式函数的用法 49 | const Box = ({ 50 | m, 51 | p, 52 | ...props 53 | }) => { 54 | const sx = { 55 | ...getMargin(m), 56 | ...getPadding(p) 57 | }; 58 | 59 | return <div {...props} style={sx}/> 60 | }; 61 | 62 | // 组件用法. 63 | const Box = () => ( 64 | <div> 65 | <Box m={2} p={3}> 66 | A box with 16px margin and 32px padding 67 | </Box> 68 | </div> 69 | ); 70 | ``` 71 | -------------------------------------------------------------------------------- /styling/04.using-npm-modules.md: -------------------------------------------------------------------------------- 1 | # 使用npm模块 2 | 对于那些比较复杂的样式/颜色转换, 使用不同的npm模块有时会是比自己造轮子更好的选择. 3 | #### Example 4 | 5 | 对于在CSS中的暗色梯度, 我们可以使用`chroma-js`这个模块 6 | ```javascript 7 | import chroma from 'chroma-js' 8 | 9 | const alpha = (color) => (a) => chroma(color).alpha(a).css(); 10 | 11 | const darken = alpha('#000'); 12 | 13 | const shade = [ 14 | darken(0), 15 | darken(1 / 8), 16 | darken(1 / 4) 17 | // More... 18 | ]; 19 | 20 | const blueAlpha = [ 21 | alpha(blue)(0), 22 | alpha(blue)(1 / 4), 23 | alpha(blue)(1 / 2), 24 | alpha(blue)(3 / 4), 25 | alpha(blue)(1) 26 | ]; 27 | ``` 28 | -------------------------------------------------------------------------------- /styling/05.base-component.md: -------------------------------------------------------------------------------- 1 | # 基础组件(样式通过props传入的组件) 2 | ### 使用基础组件 3 | 在React中使用组合的思想去构建我们的UI会带来很大的灵活性, 因为我们的组件从另一个角度来看都是函数. 4 | 通过改变组件中的props进而改变组件的样式, 我们能让组件更加的可复用. 5 | 6 | 我们把color和backgroundColor属性作为组件的props传入, 另外我们新加了一个props来调整padding top和padding bottom. 7 | ```javascript 8 | const Button = ({ 9 | big, 10 | color = colors.white, 11 | backgroundColor = colors.blue, 12 | ...props 13 | }) => { 14 | const sx = { 15 | fontFamily: 'inherit', 16 | fontSize: 'inherit', 17 | fontWeight: bold, 18 | textDecoration: 'none', 19 | display: 'inline-block', 20 | margin: 0, 21 | paddingTop: big ? space[2] : space[1], 22 | paddingBottom: big ? space[2] : space[1], 23 | paddingLeft: space[2], 24 | paddingRight: space[2], 25 | border: 0, 26 | color, 27 | backgroundColor, 28 | WebkitAppearance: 'none', 29 | MozAppearance: 'none' 30 | }; 31 | 32 | return ( 33 | <button {...props} style={sx}/> 34 | ) 35 | }; 36 | ``` 37 | #### 用法 38 | ```javascript 39 | const Button = () => ( 40 | <div> 41 | <Button> 42 | Blue Button 43 | </Button> 44 | <Button big backgroundColor={colors.red}> 45 | Big Red Button 46 | </Button> 47 | </div> 48 | ); 49 | 50 | // 通过Button组件的API去改变Button组件的样式, 51 | // 我们能得到样式各异的Button. 52 | const ButtonBig = (props) => <Button {...props} big/>; 53 | const ButtonGreen = (props) => <Button {...props} backgroundColor={colors.green}/>; 54 | const ButtonRed = (props) => <Button {...props} backgroundColor={colors.red}/>; 55 | const ButtonOutline = (props) => <Button {...props} outline/>; 56 | ``` 57 | -------------------------------------------------------------------------------- /styling/06.layout-component.md: -------------------------------------------------------------------------------- 1 | # 布局组件 2 | 我们拓展了[基础组件](./05.base-component.md)的概念, 创造出了布局组件. 3 | 4 | #### 例子 5 | ```javascript 6 | const Grid = (props) => ( 7 | <Box {...props} 8 | display='inline-block' 9 | verticalAlign='top' 10 | px={2}/> 11 | ); 12 | 13 | const Half = (props) => ( 14 | <Grid {...props} 15 | width={1 / 2}/> 16 | ); 17 | 18 | const Third = (props) => ( 19 | <Grid {...props} 20 | width={1 / 3}/> 21 | ); 22 | 23 | const Quarter = (props) => ( 24 | <Grid {...props} 25 | width={1 / 4}/> 26 | ); 27 | 28 | const Flex = (props) => ( 29 | <Box {...props} 30 | display='flex'/> 31 | ); 32 | 33 | const FlexAuto = (props) => ( 34 | <Box {...props} 35 | flex='1 1 auto'/> 36 | ); 37 | ``` 38 | 39 | #### 用法 40 | ```javascript 41 | const Layout = () => ( 42 | <div> 43 | <div> 44 | <Half>Half width column</Half> 45 | <Half>Half width column</Half> 46 | </div> 47 | <div> 48 | <Third>Third width column</Third> 49 | <Third>Third width column</Third> 50 | <Third>Third width column</Third> 51 | </div> 52 | <div> 53 | <Quarter>Quarter width column</Quarter> 54 | <Quarter>Quarter width column</Quarter> 55 | <Quarter>Quarter width column</Quarter> 56 | <Quarter>Quarter width column</Quarter> 57 | </div> 58 | </div> 59 | ); 60 | ``` 61 | 62 | ### 参考资料: 63 | - [Github: React Layout components](https://github.com/rofrischmann/react-layout-components) 64 | - [Leveling Up With React: Container Components](https://css-tricks.com/learning-react-container-components/) 65 | - [Container Components and Stateless Functional Components in React](Leveling Up With React: Container Components) -------------------------------------------------------------------------------- /styling/07.typography-component.md: -------------------------------------------------------------------------------- 1 | # 排版组件 2 | 我们拓展了[基础组件](./05.base-component.md)的概念创造了排版组件. 3 | 这个模式能保证一致性以及你的样式足够的纯净. 4 | 5 | #### 例子 6 | ```javascript 7 | import React from 'react'; 8 | import { alternateFont, typeScale, boldFontWeight } from './styles'; 9 | 10 | const Text = ({ 11 | tag = 'span', 12 | size = 4, 13 | alt, 14 | center, 15 | bold, 16 | caps, 17 | ...props 18 | }) => { 19 | const Tag = tag; 20 | const sx = { 21 | fontFamily: alt ? alternateFont : null, 22 | fontSize: typeScale[size], 23 | fontWeight: bold ? boldFontWeight : null, 24 | textAlign: center ? 'center' : null, 25 | textTransform: caps ? 'uppercase' : null 26 | }; 27 | 28 | return <Tag {...props} style={sx}/> 29 | }; 30 | 31 | const LeadText = (props) => <Text {...props} tag='p' size={3}/>; 32 | const Caps = (props) => <Text {...props} caps/>; 33 | const MetaText = (props) => <Text {...props} size={5} caps/>; 34 | const AltParagraph = (props) => <Text {...props} tag='p' alt/>; 35 | 36 | const CapsButton = ({ children, ...props }) => ( 37 | <Button {...props}> 38 | <Caps> 39 | {children} 40 | </Caps> 41 | </Button> 42 | ); 43 | ``` 44 | #### 用法 45 | ```javascript 46 | const TypographyComponent = () => ( 47 | <div> 48 | <LeadText> 49 | This is a lead with some<Caps>all caps</Caps>. 50 | It has a larger font size than the default paragraph. 51 | </LeadText> 52 | <MetaText> 53 | This is smaller text, like form helper copy. 54 | </MetaText> 55 | </div> 56 | ); 57 | ``` 58 | -------------------------------------------------------------------------------- /styling/08.HOC-for-styling.md: -------------------------------------------------------------------------------- 1 | # 使用高阶组件来改变样式 2 | 有时有一些组件可能只需要很少的一部分state来维护一些很简单的交互, 我们也有充足的理由把这些组件作为可复用的组件. 3 | 4 | 例子. Carousel组件的交互 5 | 6 | #### 例子: Carousel 7 | 8 | 这个高阶组件会持有当前幻灯片的index并且提供前进和回退的功能. 9 | ```javascript 10 | // 高阶组件 11 | import React from 'react' 12 | // 这个高阶组件其实可以被命名的更加通俗易懂, 比如Counter或者Cycle 13 | const CarouselContainer = (Comp) => { 14 | class Carousel extends React.Component { 15 | constructor() { 16 | super(); 17 | this.state = { 18 | index: 0 19 | }; 20 | this.previous = () => { 21 | const { index } = this.state; 22 | if (index > 0) { 23 | this.setState({index: index - 1}) 24 | } 25 | }; 26 | 27 | this.next = () => { 28 | const { index } = this.state; 29 | this.setState({index: index + 1}) 30 | } 31 | } 32 | 33 | render() { 34 | return ( 35 | <Comp 36 | {...this.props} 37 | {...this.state} 38 | previous={this.previous} 39 | next={this.next}/> 40 | ) 41 | } 42 | } 43 | return Carousel 44 | }; 45 | export default CarouselContainer; 46 | ``` 47 | #### 使用高阶组件 48 | ```javascript 49 | // 纯UI component 50 | const Carousel = ({ index, ...props }) => { 51 | const length = props.length || props.children.length || 0; 52 | 53 | const sx = { 54 | root: { 55 | overflow: 'hidden' 56 | }, 57 | inner: { 58 | whiteSpace: 'nowrap', 59 | height: '100%', 60 | transition: 'transform .2s ease-out', 61 | transform: `translateX(${index % length * -100}%)` 62 | }, 63 | child: { 64 | display: 'inline-block', 65 | verticalAlign: 'middle', 66 | whiteSpace: 'normal', 67 | outline: '1px solid red', 68 | width: '100%', 69 | height: '100%' 70 | } 71 | }; 72 | 73 | const children = React.Children.map(props.children, (child, i) => { 74 | return ( 75 | <div style={sx.child}> 76 | {child} 77 | </div> 78 | ) 79 | }); 80 | 81 | return ( 82 | <div style={sx.root}> 83 | <div style={sx.inner}> 84 | {children} 85 | </div> 86 | </div> 87 | ) 88 | }; 89 | 90 | // 最后的Carousel组件 91 | const HeroCarousel = (props) => { 92 | return ( 93 | <div> 94 | <Carousel index={props.index}> 95 | <div>Slide one</div> 96 | <div>Slide two</div> 97 | <div>Slide three</div> 98 | </Carousel> 99 | <Button 100 | onClick={props.previous} 101 | children='Previous'/> 102 | <Button 103 | onClick={props.next} 104 | children='Next'/> 105 | </div> 106 | ) 107 | }; 108 | 109 | // 我们通过在外面包裹一层container组件来给这个组件带来更多的功能. 110 | export default CarouselContainer(HeroCarousel) 111 | ``` 112 | 通过保持样式和交互状态的分离, 基于同一个carsousel组件, 我们可以创造任意数量不同的更复杂的carousel组件, 113 | 114 | #### 用法 115 | ```javascript 116 | const Carousel = () => ( 117 | <div> 118 | <HeroCarousel /> 119 | </div> 120 | ); 121 | ``` 122 | -------------------------------------------------------------------------------- /styling/README.md: -------------------------------------------------------------------------------- 1 | # React中的样式 2 | 3 | 在这一个章节中我们会提供一些CSS In JS的实践. 4 | 5 | 如果你还不太明白为什么要CSS In Js, 作者vasan推荐你看一看下面的 6 | [talk](http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html) by [Vjeux](https://twitter.com/Vjeux) 7 | 8 | ## 相关文章 9 | 10 | [Patterns for style composition in React](http://jxnblk.com/writing/posts/patterns-for-style-composition-in-react/) 11 | 12 | [Inline style vs stylesheet performance](https://www.ctheu.com/2015/08/17/react-inline-styles-vs-css-stupid-benchmark/) -------------------------------------------------------------------------------- /ux-variations/01.composing-variations.md: -------------------------------------------------------------------------------- 1 | # 使用组合去实现不同的交互功能 2 | 3 | 在React的实践中, 将小的可复用的组件组合成功能更复杂的组件是一种推荐的实践. 4 | 5 | **我们怎么保证组件的复用性?** 6 | - 我们需要保证组件是纯的UI组件, 传入同样的props总会渲染出相同的组件.(木偶组件) 7 | 8 | **复用意味着什么?** 9 | - 在组件内部没有与外部的数据交互(这应该在Redux中完成). 10 | - 如果有需要从API获取的数据, 请使用redux-thunk. redux-thunk和redux容器是相互隔离的, 我们可以通过redux-thunk获取数据, 然后通过redux容器将数据以props的形式传递到我们的子组件里面去. 11 | 12 | 如果我们在`render`函数里面使用了很多`renderxxx()`该怎么办? 13 | - 如果使用或者创建了很多renderxxx()函数, 这往往意味着这些renderxxx()函数是可以变成可复用的小组件的. 14 | 15 | ### 例子 16 | 千变万化的登录页面 17 | 18 | 用户登录页面可能变化很多, 根据用户登录情况会打开/关闭某些功能或者显示/隐藏一些元素. 19 | 20 | 这些打开/关闭的功能应该被封装在一个子组件里面, 而一些无论什么情况下都要显示的元素(header/footer), 我们可以抽取出来放在父组件中进行复用. 21 | ```javascript 22 | import React, { Component } from "react"; 23 | import PropTypes from 'prop-types'; 24 | import SignIn from "./sign-in"; 25 | 26 | class MemberSignIn extends Component { 27 | _renderMemberJoinLinks() { 28 | return ( 29 | <div className="member-signup-links"> 30 | ... 31 | </div> 32 | ); 33 | } 34 | 35 | _routeTo() { 36 | // Routing logic here 37 | } 38 | 39 | render() { 40 | const {forgotEmailRoute,forgotPwdRoute, showMemberSignupLinks} = this.props; 41 | return ( 42 | <div> 43 | <SignIn 44 | onForgotPasswordRequested={this._routeTo(forgotPwdRoute)} 45 | onForgotEmailRequested={this._routeTo(forgotEmailRoute)}> 46 | {this.props.children} 47 | {showMemberSignupLinks && this._renderMemberJoinLinks()} 48 | </SignIn> 49 | </div> 50 | ); 51 | } 52 | } 53 | 54 | export default MemberSignIn; 55 | ``` 56 | 57 | ### 参考资料: 58 | - [Slides from my talk: Building Multi-tenant UI with React](https://speakerdeck.com/vasa/building-multitenant-ui-with-react-dot-js) -------------------------------------------------------------------------------- /ux-variations/02.toggle-ui-elements.md: -------------------------------------------------------------------------------- 1 | # 样式开关 2 | 3 | 我们也可以通过开/关某个功能来实现我们我们组件的多样性. 而这些开关我们可以通过props传递进我们的组件. 4 | 5 | ### Toggle并不是万能的: 6 | 如果抱着toggle的想法, 我们很容易将一个组件设计成一个万能组件, 通过传入一大堆props来完成多样性, 这其实并不是特别好的实践. 所以我们最好遵守以下两点: 7 | - 只将必须的props传入我们的组件, 而且当props太多时需要考虑抽取子组件. 8 | - 不要违背单一职责原则. 9 | 10 | #### 例子 11 | 在登录表单中显示/隐藏 password 12 | 13 | ```javascript 14 | class PasswordField extends Component { 15 | render() { 16 | const { 17 | password, 18 | showHidePassword, 19 | showErrorOnTop, 20 | showLabels, 21 | shouldComplyAda 22 | } = this.props; 23 | return ( 24 | <div> 25 | <Password 26 | field={password} 27 | label="Password" 28 | showErrorOnTop={showErrorOnTop} 29 | placeholder={shouldComplyAda ? "" : "Password"} 30 | showLabel={showLabels} 31 | showHidePassword={showHidePassword} 32 | /> 33 | </div> 34 | ); 35 | } 36 | } 37 | ``` 38 | 39 | ### 参考资料: 40 | - [Slides from my talk: Building Multi-tenant UI with React](https://speakerdeck.com/vasa/building-multitenant-ui-with-react-dot-js) 41 | -------------------------------------------------------------------------------- /ux-variations/03.HOC-feature-toggles.md: -------------------------------------------------------------------------------- 1 | # 用高阶组件去实现功能开关 2 | 3 | 使用高阶组件去实现我们的toggle, 从而实现组件的多样性. 4 | 5 | 比如下面的例子, 实现某个功能的开/关 6 | 7 | ```javascript 8 | // featureToggle.js 9 | const isFeatureOn = function (featureName) { 10 | // return true or false 11 | }; 12 | 13 | import { isFeatureOn } from './featureToggle'; 14 | 15 | const toggleOn = (featureName, ComposedComponent) => class HOC extends Component { 16 | render() { 17 | return isFeatureOn(featureName) ? <ComposedComponent {...this.props} /> : null; 18 | } 19 | }; 20 | 21 | // 用法 22 | import AdsComponent from './Ads' 23 | const Ads = toggleOn('ads', AdsComponent); 24 | ``` 25 | -------------------------------------------------------------------------------- /ux-variations/04.HOC-props-proxy.md: -------------------------------------------------------------------------------- 1 | # 使用高阶组件做props代理. 2 | Props代理 3 | 4 | 使用高阶组件能帮助我们对于传入的props进行修饰后传入真正的组件(类似于middleware的概念) 5 | 6 | ```javascript 7 | function HOC(WrappedComponent) { 8 | return class Test extends Component { 9 | render() { 10 | const newProps = { 11 | title: 'New Header', 12 | footer: false, 13 | showFeatureX: false, 14 | showFeatureY: true 15 | }; 16 | 17 | return <WrappedComponent {...this.props} {...newProps} /> 18 | } 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /ux-variations/05.wrapper-components.md: -------------------------------------------------------------------------------- 1 | # Wrapper Components 2 | 对我们的组件进行包装来适配不同的样式/交互行为. 3 | 如果你想处理`<div>`或者其他HTML标签的话, 你可以使用组合. 4 | 5 | 当你创建React实例的时候, 你能在jsx标签内包裹其他的React组件或者任意的JavaScript表达式. 6 | 父组件通过this.props.children能访问到其包裹的子组件. 7 | 8 | ```javascript 9 | const SampleComponent = () => { 10 | <Parent> 11 | <Child /> 12 | </Parent> 13 | }; 14 | 15 | const Parent = () => { 16 | // 你能使用class 'bla'或者其他的class来给子组件加上不同的样式. 17 | <div className="bla"> 18 | {this.props.children} 19 | </div> 20 | }; 21 | ``` 22 | 23 | 值得一提的是, 包裹组件同样可以通过接收一个tag名来生成对应的HTML标签. 24 | 但是一般情况下我们不推荐这么做, 因为这样做的话你就不能添加属性或者传入props了. 25 | 26 | ```javascript 27 | const SampleComponent = () => { 28 | <Wrap tagName="div" content="Hello World" /> 29 | }; 30 | 31 | const Wrap = ({ tagName, content }) => { 32 | const Tag = `${tagName}` // 变量名必须大写开头因为这是一个组件. 33 | return <Tag>{content}</Tag> 34 | } 35 | ``` 36 | 37 | ### 参考资料: 38 | - [Slides from my talk: Building Multi-tenant UI with React](https://speakerdeck.com/vasa/building-multitenant-ui-with-react-dot-js) 39 | -------------------------------------------------------------------------------- /ux-variations/06.display-order-variations.md: -------------------------------------------------------------------------------- 1 | # 以不同的顺序展示我们的UI组件 2 | 3 | 我们使用props来定下我们显示的顺序. 我们的组件基于我们排好序的props进行渲染. 4 | ```javascript 5 | class PageSections extends Component { 6 | render() { 7 | const pageItems = this.props.contentOrder.map( 8 | (content) => { 9 | const renderFunc = this.contentOrderMap[content]; 10 | return (typeof renderFunc === 'function') ? renderFunc() : null; 11 | } 12 | ); 13 | 14 | return ( 15 | <div className="page-content"> 16 | {pageItems} 17 | </div> 18 | ) 19 | } 20 | } 21 | ``` 22 | 23 | ### 参考资料: 24 | - [Slides from my talk: Building Multi-tenant UI with React](https://speakerdeck.com/vasa/building-multitenant-ui-with-react-dot-js) -------------------------------------------------------------------------------- /ux-variations/README.md: -------------------------------------------------------------------------------- 1 | # Handling UX variations for multiple brands and apps 2 | 3 | 4 | ## [Vasa做的关于react组件复用的演讲](https://speakerdeck.com/vasa/building-multitenant-ui-with-react-dot-js) 5 | 6 | 7 | 以下是一些有助于编写高复用性react组件的通用编码原则。 8 | 9 | ## 单功能原则 10 | 11 | **使用react时** 12 | 13 | 组件或容器的代码在根本上必须只负责一块UI功能。 14 | 15 | * 以设计收货地址组件为例 16 | 17 | * 收货地址组件可以拆分为地址组件(只含有地址相关内容),姓名组件(包括姓氏和名字),电话组件,省,市和邮政编码组件。 18 | 19 | **使用redux时** 20 | 21 | All API related call go into Redux thunks/other async handling sections (redux-promise, sagas etc)所有API相关操作可以使用redux或者其他异步模块(如redux-promise, sagas等)来处理。模块可以如下划分: 22 | 23 | * 一些模块只负责在AJAX请求成功或失败的时候派发动作。 24 | 25 | * 一些模块通过promise来接收。 26 | 27 | ## 让组件保持简单 (KISS) 28 | 29 | * 如果组件根本不需要状态,那么就使用函数定义的无状态组件。 30 | 31 | * 从性能上来说,**函数定义的无状态组件 > ES6 class 定义的组件 > 通过React.createClass()定义的组件**。 32 | 33 | * 仅传递组件所需要的属性。只有当属性列表太长时,才使用{...this.props}进行传递。 34 | 35 | * 如果组件里面有太多的判断逻辑(if-else语句)通常意味着这个组件需要被拆分成更细的组件或模块。 36 | 37 | * 还有一点,使用明确的命名能够让开发者明白它的功能,有助于组件复用。 38 | 39 | ## 相关文章 40 | 41 | [Building React Components for Multiple Brands and Applications](https://medium.com/walmartlabs/building-react-components-for-multiple-brands-and-applications-7e9157a39db4) 42 | 43 | --------------------------------------------------------------------------------